Merge branch 'rewrite-update_engine' into merge-update_engine
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6dd317
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# Build/test generated files
+/*.pub.pem
+/app.info
+/delta_generator
+/html/
+/test_http_server
+/update_engine
+/update_engine.dbusclient.h
+/update_engine.dbusserver.h
+/update_engine_client
+/update_engine_unittests
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..fafbecc
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+deymo@chromium.org
+garnold@chromium.org
+zeuthen@chromium.org
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
new file mode 100644
index 0000000..e9fe2d5
--- /dev/null
+++ b/UpdateEngine.conf
@@ -0,0 +1,67 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+  "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+  <policy user="root">
+    <allow own="org.chromium.UpdateEngine" />
+    <allow send_destination="org.chromium.UpdateEngine" />
+  </policy>
+  <policy user="chronos">
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="AttemptUpdate"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="AttemptUpdateWithFlags"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="AttemptRollback"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="CanRollback"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetRollbackPartition"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetKernelDevices"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="ResetStatus"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetStatus"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="RebootIfNeeded"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetChannel"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetChannel"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetP2PUpdatePermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetP2PUpdatePermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetUpdateOverCellularPermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetUpdateOverCellularPermission"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetDurationSinceUpdate"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetPrevVersion"/>
+    <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" />
+  </policy>
+  <policy user="power">
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetStatus"/>
+  </policy>
+</busconfig>
diff --git a/WATCHLISTS b/WATCHLISTS
new file mode 100644
index 0000000..bcce0de
--- /dev/null
+++ b/WATCHLISTS
@@ -0,0 +1,14 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+  'WATCHLIST_DEFINITIONS': {
+    'all': {
+      'filepath': '.',
+    },
+  },
+  'WATCHLISTS': {
+    'all': ['adlr@chromium.org', 'petkov@chromium.org']
+  },
+}
diff --git a/action.h b/action.h
new file mode 100644
index 0000000..53786d1
--- /dev/null
+++ b/action.h
@@ -0,0 +1,206 @@
+// Copyright (c) 2009 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_ACTION_H_
+#define UPDATE_ENGINE_ACTION_H_
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/action_processor.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+//
+// Readers may want to consult this wiki page from the Update Engine site:
+// http://code.google.com/p/update-engine/wiki/ActionProcessor
+// Although it's referring to the Objective-C KSAction* classes, much
+// applies here as well.
+//
+// How it works:
+//
+// First off, there is only one thread and all I/O should be asynchronous.
+// A glib main loop blocks whenever there is no work to be done. This happens
+// where there is no CPU work to be done and no I/O ready to transfer in or
+// out. Two kinds of events can wake up the main loop: timer alarm or file
+// descriptors. If either of these happens, glib finds out the owner of what
+// fired and calls the appropriate code to handle it. As such, all the code
+// in the Action* classes and the code that is calls is non-blocking.
+//
+// An ActionProcessor contains a queue of Actions to perform. When
+// ActionProcessor::StartProcessing() is called, it executes the first action.
+// Each action tells the processor when it has completed, which causes the
+// Processor to execute the next action. ActionProcessor may have a delegate
+// (an object of type ActionProcessorDelegate). If it does, the delegate
+// is called to be notified of events as they happen.
+//
+// ActionPipe classes
+//
+// See action_pipe.h
+//
+// ActionTraits
+//
+// We need to use an extra class ActionTraits. ActionTraits is a simple
+// templated class that contains only two typedefs: OutputObjectType and
+// InputObjectType. Each action class also has two typedefs of the same name
+// that are of the same type. So, to get the input/output types of, e.g., the
+// DownloadAction class, we look at the type of
+// DownloadAction::InputObjectType.
+//
+// Each concrete Action class derives from Action<T>. This means that during
+// template instantiation of Action<T>, T is declared but not defined, which
+// means that T::InputObjectType (and OutputObjectType) is not defined.
+// However, the traits class is constructed in such a way that it will be
+// template instantiated first, so Action<T> *can* find the types it needs by
+// consulting ActionTraits<T>::InputObjectType (and OutputObjectType).
+// This is why the ActionTraits classes are needed.
+
+namespace chromeos_update_engine {
+
+// It is handy to have a non-templated base class of all Actions.
+class AbstractAction {
+ public:
+  AbstractAction() : processor_(nullptr) {}
+  virtual ~AbstractAction() = default;
+
+  // Begin performing the action. Since this code is asynchronous, when this
+  // method returns, it means only that the action has started, not necessarily
+  // completed. However, it's acceptable for this method to perform the
+  // action synchronously; Action authors should understand the implications
+  // of synchronously performing, though, because this is a single-threaded
+  // app, the entire process will be blocked while the action performs.
+  //
+  // When the action is complete, it must call
+  // ActionProcessor::ActionComplete(this); to notify the processor that it's
+  // done.
+  virtual void PerformAction() = 0;
+
+  // Called on ActionProcess::ActionComplete() by ActionProcessor.
+  virtual void ActionCompleted(ErrorCode code) {}
+
+  // Called by the ActionProcessor to tell this Action which processor
+  // it belongs to.
+  void SetProcessor(ActionProcessor* processor) {
+    if (processor)
+      CHECK(!processor_);
+    else
+      CHECK(processor_);
+    processor_ = processor;
+  }
+
+  // Returns true iff the action is the current action of its ActionProcessor.
+  bool IsRunning() const {
+    if (!processor_)
+      return false;
+    return processor_->current_action() == this;
+  }
+
+  // Called on asynchronous actions if canceled. Actions may implement if
+  // there's any cleanup to do. There is no need to call
+  // ActionProcessor::ActionComplete() because the processor knows this
+  // action is terminating.
+  // Only the ActionProcessor should call this.
+  virtual void TerminateProcessing() {}
+
+  // These methods are useful for debugging. TODO(adlr): consider using
+  // std::type_info for this?
+  // Type() returns a string of the Action type. I.e., for DownloadAction,
+  // Type() would return "DownloadAction".
+  virtual std::string Type() const = 0;
+
+ protected:
+  // A weak pointer to the processor that owns this Action.
+  ActionProcessor* processor_;
+};
+
+// Forward declare a couple classes we use.
+template<typename T>
+class ActionPipe;
+template<typename T>
+class ActionTraits;
+
+template<typename SubClass>
+class Action : public AbstractAction {
+ public:
+  ~Action() override {}
+
+  // Attaches an input pipe to this Action. This is optional; an Action
+  // doesn't need to have an input pipe. The input pipe must be of the type
+  // of object that this class expects.
+  // This is generally called by ActionPipe::Bond()
+  void set_in_pipe(
+      // this type is a fancy way of saying: a shared_ptr to an
+      // ActionPipe<InputObjectType>.
+      const std::shared_ptr<ActionPipe<
+          typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
+    in_pipe_ = in_pipe;
+  }
+
+  // Attaches an output pipe to this Action. This is optional; an Action
+  // doesn't need to have an output pipe. The output pipe must be of the type
+  // of object that this class expects.
+  // This is generally called by ActionPipe::Bond()
+  void set_out_pipe(
+      // this type is a fancy way of saying: a shared_ptr to an
+      // ActionPipe<OutputObjectType>.
+      const std::shared_ptr<ActionPipe<
+          typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
+    out_pipe_ = out_pipe;
+  }
+
+  // Returns true iff there is an associated input pipe. If there's an input
+  // pipe, there's an input object, but it may have been constructed with the
+  // default ctor if the previous action didn't call SetOutputObject().
+  bool HasInputObject() const { return in_pipe_.get(); }
+
+  // returns a const reference to the object in the input pipe.
+  const typename ActionTraits<SubClass>::InputObjectType& GetInputObject()
+      const {
+    CHECK(HasInputObject());
+    return in_pipe_->contents();
+  }
+
+  // Returns true iff there's an output pipe.
+  bool HasOutputPipe() const {
+    return out_pipe_.get();
+  }
+
+  // Copies the object passed into the output pipe. It will be accessible to
+  // the next Action via that action's input pipe (which is the same as this
+  // Action's output pipe).
+  void SetOutputObject(
+      const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
+    CHECK(HasOutputPipe());
+    out_pipe_->set_contents(out_obj);
+  }
+
+  // Returns a reference to the object sitting in the output pipe.
+  const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() {
+    CHECK(HasOutputPipe());
+    return out_pipe_->contents();
+  }
+
+ protected:
+  // We use a shared_ptr to the pipe. shared_ptr objects destroy what they
+  // point to when the last such shared_ptr object dies. We consider the
+  // Actions on either end of a pipe to "own" the pipe. When the last Action
+  // of the two dies, the ActionPipe will die, too.
+  std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
+      in_pipe_;
+  std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
+      out_pipe_;
+};
+
+};  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_ACTION_H_
diff --git a/action_pipe.h b/action_pipe.h
new file mode 100644
index 0000000..b84b0f6
--- /dev/null
+++ b/action_pipe.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2009 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_ACTION_PIPE_H_
+#define UPDATE_ENGINE_ACTION_PIPE_H_
+
+#include <stdio.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// This class serves as a temporary holding area for an object passed out
+// from one Action and into another Action. It's templated so that it may
+// contain any type of object that an Action outputs/inputs. Actions
+// cannot be bonded (i.e., connected with a pipe) if their output/input
+// object types differ (a compiler error will result).
+//
+// An ActionPipe is generally created with the Bond() method and owned by
+// the two Action objects. a shared_ptr is used so that when the last Action
+// pointing to an ActionPipe dies, the ActionPipe dies, too.
+
+namespace chromeos_update_engine {
+
+// Used by Actions an InputObjectType or OutputObjectType to specify that
+// for that type, no object is taken/given.
+class NoneType {};
+
+template<typename T>
+class Action;
+
+template<typename ObjectType>
+class ActionPipe {
+ public:
+  virtual ~ActionPipe() {}
+
+  // This should be called by an Action on its input pipe.
+  // Returns a reference to the stored object.
+  const ObjectType& contents() const { return contents_; }
+
+  // This should be called by an Action on its output pipe.
+  // Stores a copy of the passed object in this pipe.
+  void set_contents(const ObjectType& contents) { contents_ = contents; }
+
+  // Bonds two Actions together with a new ActionPipe. The ActionPipe is
+  // jointly owned by the two Actions and will be automatically destroyed
+  // when the last Action is destroyed.
+  template<typename FromAction, typename ToAction>
+  static void Bond(FromAction* from, ToAction* to) {
+    std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
+    from->set_out_pipe(pipe);
+
+    to->set_in_pipe(pipe);  // If you get an error on this line, then
+    // it most likely means that the From object's OutputObjectType is
+    // different from the To object's InputObjectType.
+  }
+
+ private:
+  ObjectType contents_;
+
+  // The ctor is private. This is because this class should construct itself
+  // via the static Bond() method.
+  ActionPipe() {}
+  DISALLOW_COPY_AND_ASSIGN(ActionPipe);
+};
+
+// Utility function
+template<typename FromAction, typename ToAction>
+void BondActions(FromAction* from, ToAction* to) {
+  // TODO(adlr): find something like this that the compiler accepts:
+  // COMPILE_ASSERT(typeof(typename FromAction::OutputObjectType) ==
+  //                typeof(typename ToAction::InputObjectType),
+  //     FromAction_OutputObjectType_doesnt_match_ToAction_InputObjectType);
+  ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
+}
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_ACTION_PIPE_H_
diff --git a/action_pipe_unittest.cc b/action_pipe_unittest.cc
new file mode 100644
index 0000000..3cdd03d
--- /dev/null
+++ b/action_pipe_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2009 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/action_pipe.h"
+
+#include <string>
+#include <gtest/gtest.h>
+#include "update_engine/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionPipeTestAction;
+
+template<>
+class ActionTraits<ActionPipeTestAction> {
+ public:
+  typedef string OutputObjectType;
+  typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionPipeTestAction : public Action<ActionPipeTestAction> {
+ public:
+  typedef string InputObjectType;
+  typedef string OutputObjectType;
+  ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+  void PerformAction() {}
+  string Type() const { return "ActionPipeTestAction"; }
+};
+
+class ActionPipeTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionPipeTest, SimpleTest) {
+  ActionPipeTestAction a, b;
+  BondActions(&a, &b);
+  a.out_pipe()->set_contents("foo");
+  EXPECT_EQ("foo", b.in_pipe()->contents());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/action_processor.cc b/action_processor.cc
new file mode 100644
index 0000000..9a27aac
--- /dev/null
+++ b/action_processor.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2009 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/action_processor.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+ActionProcessor::ActionProcessor()
+    : current_action_(nullptr), delegate_(nullptr) {}
+
+ActionProcessor::~ActionProcessor() {
+  if (IsRunning()) {
+    StopProcessing();
+  }
+  for (std::deque<AbstractAction*>::iterator it = actions_.begin();
+       it != actions_.end(); ++it) {
+    (*it)->SetProcessor(nullptr);
+  }
+}
+
+void ActionProcessor::EnqueueAction(AbstractAction* action) {
+  actions_.push_back(action);
+  action->SetProcessor(this);
+}
+
+void ActionProcessor::StartProcessing() {
+  CHECK(!IsRunning());
+  if (!actions_.empty()) {
+    current_action_ = actions_.front();
+    LOG(INFO) << "ActionProcessor::StartProcessing: "
+              << current_action_->Type();
+    actions_.pop_front();
+    current_action_->PerformAction();
+  }
+}
+
+void ActionProcessor::StopProcessing() {
+  CHECK(IsRunning());
+  CHECK(current_action_);
+  current_action_->TerminateProcessing();
+  CHECK(current_action_);
+  current_action_->SetProcessor(nullptr);
+  LOG(INFO) << "ActionProcessor::StopProcessing: aborted "
+            << current_action_->Type();
+  current_action_ = nullptr;
+  if (delegate_)
+    delegate_->ProcessingStopped(this);
+}
+
+void ActionProcessor::ActionComplete(AbstractAction* actionptr,
+                                     ErrorCode code) {
+  CHECK_EQ(actionptr, current_action_);
+  if (delegate_)
+    delegate_->ActionCompleted(this, actionptr, code);
+  string old_type = current_action_->Type();
+  current_action_->ActionCompleted(code);
+  current_action_->SetProcessor(nullptr);
+  current_action_ = nullptr;
+  if (actions_.empty()) {
+    LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+                 " type " << old_type;
+  } else if (code != ErrorCode::kSuccess) {
+    LOG(INFO) << "ActionProcessor::ActionComplete: " << old_type
+              << " action failed. Aborting processing.";
+    actions_.clear();
+  }
+  if (actions_.empty()) {
+    LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+                 " type " << old_type;
+    if (delegate_) {
+      delegate_->ProcessingDone(this, code);
+    }
+    return;
+  }
+  current_action_ = actions_.front();
+  actions_.pop_front();
+  LOG(INFO) << "ActionProcessor::ActionComplete: finished " << old_type
+            << ", starting " << current_action_->Type();
+  current_action_->PerformAction();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/action_processor.h b/action_processor.h
new file mode 100644
index 0000000..121e0e1
--- /dev/null
+++ b/action_processor.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2011 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_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_ACTION_PROCESSOR_H_
+
+#include <deque>
+
+#include <base/macros.h>
+
+#include "update_engine/error_code.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// See action.h for an overview of this class and other Action* classes.
+
+// An ActionProcessor keeps a queue of Actions and processes them in order.
+
+namespace chromeos_update_engine {
+
+class AbstractAction;
+class ActionProcessorDelegate;
+
+class ActionProcessor {
+ public:
+  ActionProcessor();
+
+  virtual ~ActionProcessor();
+
+  // Starts processing the first Action in the queue. If there's a delegate,
+  // when all processing is complete, ProcessingDone() will be called on the
+  // delegate.
+  virtual void StartProcessing();
+
+  // Aborts processing. If an Action is running, it will have
+  // TerminateProcessing() called on it. The Action that was running
+  // will be lost and must be re-enqueued if this Processor is to use it.
+  void StopProcessing();
+
+  // Returns true iff an Action is currently processing.
+  bool IsRunning() const { return nullptr != current_action_; }
+
+  // Adds another Action to the end of the queue.
+  virtual void EnqueueAction(AbstractAction* action);
+
+  // Sets/gets the current delegate. Set to null to remove a delegate.
+  ActionProcessorDelegate* delegate() const { return delegate_; }
+  void set_delegate(ActionProcessorDelegate *delegate) {
+    delegate_ = delegate;
+  }
+
+  // Returns a pointer to the current Action that's processing.
+  AbstractAction* current_action() const {
+    return current_action_;
+  }
+
+  // Called by an action to notify processor that it's done. Caller passes self.
+  void ActionComplete(AbstractAction* actionptr, ErrorCode code);
+
+ private:
+  // Actions that have not yet begun processing, in the order in which
+  // they'll be processed.
+  std::deque<AbstractAction*> actions_;
+
+  // A pointer to the currently processing Action, if any.
+  AbstractAction* current_action_;
+
+  // A pointer to the delegate, or null if none.
+  ActionProcessorDelegate *delegate_;
+  DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
+};
+
+// A delegate object can be used to be notified of events that happen
+// in an ActionProcessor. An instance of this class can be passed to an
+// ActionProcessor to register itself.
+class ActionProcessorDelegate {
+ public:
+  virtual ~ActionProcessorDelegate() = default;
+
+  // Called when all processing in an ActionProcessor has completed. A pointer
+  // to the ActionProcessor is passed. |code| is set to the exit code of the
+  // last completed action.
+  virtual void ProcessingDone(const ActionProcessor* processor,
+                              ErrorCode code) {}
+
+  // Called when processing has stopped. Does not mean that all Actions have
+  // completed. If/when all Actions complete, ProcessingDone() will be called.
+  virtual void ProcessingStopped(const ActionProcessor* processor) {}
+
+  // Called whenever an action has finished processing, either successfully
+  // or otherwise.
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
+                               ErrorCode code) {}
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_ACTION_PROCESSOR_H_
diff --git a/action_processor_unittest.cc b/action_processor_unittest.cc
new file mode 100644
index 0000000..64e847d
--- /dev/null
+++ b/action_processor_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2009 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/action_processor.h"
+
+#include <string>
+#include <gtest/gtest.h>
+#include "update_engine/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionProcessorTestAction;
+
+template<>
+class ActionTraits<ActionProcessorTestAction> {
+ public:
+  typedef string OutputObjectType;
+  typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionProcessorTestAction : public Action<ActionProcessorTestAction> {
+ public:
+  typedef string InputObjectType;
+  typedef string OutputObjectType;
+  ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+  ActionProcessor* processor() { return processor_; }
+  void PerformAction() {}
+  void CompleteAction() {
+    ASSERT_TRUE(processor());
+    processor()->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  string Type() const { return "ActionProcessorTestAction"; }
+};
+
+class ActionProcessorTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionProcessorTest, SimpleTest) {
+  ActionProcessorTestAction action;
+  ActionProcessor action_processor;
+  EXPECT_FALSE(action_processor.IsRunning());
+  action_processor.EnqueueAction(&action);
+  EXPECT_FALSE(action_processor.IsRunning());
+  EXPECT_FALSE(action.IsRunning());
+  action_processor.StartProcessing();
+  EXPECT_TRUE(action_processor.IsRunning());
+  EXPECT_TRUE(action.IsRunning());
+  EXPECT_EQ(action_processor.current_action(), &action);
+  action.CompleteAction();
+  EXPECT_FALSE(action_processor.IsRunning());
+  EXPECT_FALSE(action.IsRunning());
+}
+
+namespace {
+class MyActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  explicit MyActionProcessorDelegate(const ActionProcessor* processor)
+      : processor_(processor),
+        processing_done_called_(false),
+        processing_stopped_called_(false),
+        action_completed_called_(false),
+        action_exit_code_(ErrorCode::kError) {}
+
+  virtual void ProcessingDone(const ActionProcessor* processor,
+                              ErrorCode code) {
+    EXPECT_EQ(processor_, processor);
+    EXPECT_FALSE(processing_done_called_);
+    processing_done_called_ = true;
+  }
+  virtual void ProcessingStopped(const ActionProcessor* processor) {
+    EXPECT_EQ(processor_, processor);
+    EXPECT_FALSE(processing_stopped_called_);
+    processing_stopped_called_ = true;
+  }
+  virtual void ActionCompleted(ActionProcessor* processor,
+                               AbstractAction* action,
+                               ErrorCode code) {
+    EXPECT_EQ(processor_, processor);
+    EXPECT_FALSE(action_completed_called_);
+    action_completed_called_ = true;
+    action_exit_code_ = code;
+  }
+
+  const ActionProcessor* processor_;
+  bool processing_done_called_;
+  bool processing_stopped_called_;
+  bool action_completed_called_;
+  ErrorCode action_exit_code_;
+};
+}  // namespace
+
+TEST(ActionProcessorTest, DelegateTest) {
+  ActionProcessorTestAction action;
+  ActionProcessor action_processor;
+  MyActionProcessorDelegate delegate(&action_processor);
+  action_processor.set_delegate(&delegate);
+
+  action_processor.EnqueueAction(&action);
+  action_processor.StartProcessing();
+  action.CompleteAction();
+  action_processor.set_delegate(nullptr);
+  EXPECT_TRUE(delegate.processing_done_called_);
+  EXPECT_TRUE(delegate.action_completed_called_);
+}
+
+TEST(ActionProcessorTest, StopProcessingTest) {
+  ActionProcessorTestAction action;
+  ActionProcessor action_processor;
+  MyActionProcessorDelegate delegate(&action_processor);
+  action_processor.set_delegate(&delegate);
+
+  action_processor.EnqueueAction(&action);
+  action_processor.StartProcessing();
+  action_processor.StopProcessing();
+  action_processor.set_delegate(nullptr);
+  EXPECT_TRUE(delegate.processing_stopped_called_);
+  EXPECT_FALSE(delegate.action_completed_called_);
+  EXPECT_FALSE(action_processor.IsRunning());
+  EXPECT_EQ(nullptr, action_processor.current_action());
+}
+
+TEST(ActionProcessorTest, ChainActionsTest) {
+  ActionProcessorTestAction action1, action2;
+  ActionProcessor action_processor;
+  action_processor.EnqueueAction(&action1);
+  action_processor.EnqueueAction(&action2);
+  action_processor.StartProcessing();
+  EXPECT_EQ(&action1, action_processor.current_action());
+  EXPECT_TRUE(action_processor.IsRunning());
+  action1.CompleteAction();
+  EXPECT_EQ(&action2, action_processor.current_action());
+  EXPECT_TRUE(action_processor.IsRunning());
+  action2.CompleteAction();
+  EXPECT_EQ(nullptr, action_processor.current_action());
+  EXPECT_FALSE(action_processor.IsRunning());
+}
+
+TEST(ActionProcessorTest, DtorTest) {
+  ActionProcessorTestAction action1, action2;
+  {
+    ActionProcessor action_processor;
+    action_processor.EnqueueAction(&action1);
+    action_processor.EnqueueAction(&action2);
+    action_processor.StartProcessing();
+  }
+  EXPECT_EQ(nullptr, action1.processor());
+  EXPECT_FALSE(action1.IsRunning());
+  EXPECT_EQ(nullptr, action2.processor());
+  EXPECT_FALSE(action2.IsRunning());
+}
+
+TEST(ActionProcessorTest, DefaultDelegateTest) {
+  // Just make sure it doesn't crash
+  ActionProcessorTestAction action;
+  ActionProcessor action_processor;
+  ActionProcessorDelegate delegate;
+  action_processor.set_delegate(&delegate);
+
+  action_processor.EnqueueAction(&action);
+  action_processor.StartProcessing();
+  action.CompleteAction();
+
+  action_processor.EnqueueAction(&action);
+  action_processor.StartProcessing();
+  action_processor.StopProcessing();
+
+  action_processor.set_delegate(nullptr);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/action_unittest.cc b/action_unittest.cc
new file mode 100644
index 0000000..a5f7b11
--- /dev/null
+++ b/action_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2010 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/action.h"
+
+#include <string>
+#include <gtest/gtest.h>
+#include "update_engine/action_processor.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionTestAction;
+
+template<>
+class ActionTraits<ActionTestAction> {
+ public:
+  typedef string OutputObjectType;
+  typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionTestAction : public Action<ActionTestAction> {
+ public:
+  typedef string InputObjectType;
+  typedef string OutputObjectType;
+  ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+  ActionProcessor* processor() { return processor_; }
+  void PerformAction() {}
+  void CompleteAction() {
+    ASSERT_TRUE(processor());
+    processor()->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  string Type() const { return "ActionTestAction"; }
+};
+
+class ActionTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionTest, SimpleTest) {
+  ActionTestAction action;
+
+  EXPECT_FALSE(action.in_pipe());
+  EXPECT_FALSE(action.out_pipe());
+  EXPECT_FALSE(action.processor());
+  EXPECT_FALSE(action.IsRunning());
+
+  ActionProcessor action_processor;
+  action_processor.EnqueueAction(&action);
+  EXPECT_EQ(&action_processor, action.processor());
+
+  action_processor.StartProcessing();
+  EXPECT_TRUE(action.IsRunning());
+  action.CompleteAction();
+  EXPECT_FALSE(action.IsRunning());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/bzip.cc b/bzip.cc
new file mode 100644
index 0000000..9b15f10
--- /dev/null
+++ b/bzip.cc
@@ -0,0 +1,118 @@
+// Copyright (c) 2010 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/bzip.h"
+
+#include <stdlib.h>
+#include <algorithm>
+#include <bzlib.h>
+#include <limits>
+
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// BzipData compresses or decompresses the input to the output.
+// Returns true on success.
+// Use one of BzipBuffToBuff*ompress as the template parameter to BzipData().
+int BzipBuffToBuffDecompress(uint8_t* out,
+                             uint32_t* out_length,
+                             const void* in,
+                             uint32_t in_length) {
+  return BZ2_bzBuffToBuffDecompress(
+      reinterpret_cast<char*>(out),
+      out_length,
+      reinterpret_cast<char*>(const_cast<void*>(in)),
+      in_length,
+      0,  // Silent verbosity
+      0);  // Normal algorithm
+}
+
+int BzipBuffToBuffCompress(uint8_t* out,
+                           uint32_t* out_length,
+                           const void* in,
+                           uint32_t in_length) {
+  return BZ2_bzBuffToBuffCompress(
+      reinterpret_cast<char*>(out),
+      out_length,
+      reinterpret_cast<char*>(const_cast<void*>(in)),
+      in_length,
+      9,  // Best compression
+      0,  // Silent verbosity
+      0);  // Default work factor
+}
+
+template<int F(uint8_t* out,
+               uint32_t* out_length,
+               const void* in,
+               uint32_t in_length)>
+bool BzipData(const void* const in,
+              const size_t in_size,
+              chromeos::Blob* const out) {
+  TEST_AND_RETURN_FALSE(out);
+  out->clear();
+  if (in_size == 0) {
+    return true;
+  }
+  // Try increasing buffer size until it works
+  size_t buf_size = in_size;
+  out->resize(buf_size);
+
+  for (;;) {
+    if (buf_size > std::numeric_limits<uint32_t>::max())
+      return false;
+    uint32_t data_size = buf_size;
+    int rc = F(out->data(), &data_size, in, in_size);
+    TEST_AND_RETURN_FALSE(rc == BZ_OUTBUFF_FULL || rc == BZ_OK);
+    if (rc == BZ_OK) {
+      // we're done!
+      out->resize(data_size);
+      return true;
+    }
+
+    // Data didn't fit; double the buffer size.
+    buf_size *= 2;
+    out->resize(buf_size);
+  }
+}
+
+}  // namespace
+
+bool BzipDecompress(const chromeos::Blob& in, chromeos::Blob* out) {
+  return BzipData<BzipBuffToBuffDecompress>(in.data(), in.size(), out);
+}
+
+bool BzipCompress(const chromeos::Blob& in, chromeos::Blob* out) {
+  return BzipData<BzipBuffToBuffCompress>(in.data(), in.size(), out);
+}
+
+namespace {
+template<bool F(const void* const in,
+                const size_t in_size,
+                chromeos::Blob* const out)>
+bool BzipString(const string& str,
+                chromeos::Blob* out) {
+  TEST_AND_RETURN_FALSE(out);
+  chromeos::Blob temp;
+  TEST_AND_RETURN_FALSE(F(str.data(), str.size(), &temp));
+  out->clear();
+  out->insert(out->end(), temp.begin(), temp.end());
+  return true;
+}
+}  // namespace
+
+bool BzipCompressString(const string& str, chromeos::Blob* out) {
+  return BzipString<BzipData<BzipBuffToBuffCompress>>(str, out);
+}
+
+bool BzipDecompressString(const string& str, chromeos::Blob* out) {
+  return BzipString<BzipData<BzipBuffToBuffDecompress>>(str, out);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/bzip.h b/bzip.h
new file mode 100644
index 0000000..af3ada4
--- /dev/null
+++ b/bzip.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 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_BZIP_H_
+#define UPDATE_ENGINE_BZIP_H_
+
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+namespace chromeos_update_engine {
+
+// Bzip2 compresses or decompresses str/in to out.
+bool BzipDecompress(const chromeos::Blob& in, chromeos::Blob* out);
+bool BzipCompress(const chromeos::Blob& in, chromeos::Blob* out);
+bool BzipCompressString(const std::string& str, chromeos::Blob* out);
+bool BzipDecompressString(const std::string& str, chromeos::Blob* out);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BZIP_H_
diff --git a/bzip_extent_writer.cc b/bzip_extent_writer.cc
new file mode 100644
index 0000000..14a0f63
--- /dev/null
+++ b/bzip_extent_writer.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2009 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/bzip_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const chromeos::Blob::size_type kOutputBufferLength = 16 * 1024;
+}
+
+bool BzipExtentWriter::Init(FileDescriptorPtr fd,
+                            const vector<Extent>& extents,
+                            uint32_t block_size) {
+  // Init bzip2 stream
+  int rc = BZ2_bzDecompressInit(&stream_,
+                                0,   // verbosity. (0 == silent)
+                                0);  // 0 = faster algo, more memory
+
+  TEST_AND_RETURN_FALSE(rc == BZ_OK);
+
+  return next_->Init(fd, extents, block_size);
+}
+
+bool BzipExtentWriter::Write(const void* bytes, size_t count) {
+  chromeos::Blob output_buffer(kOutputBufferLength);
+
+  // Copy the input data into |input_buffer_| only if |input_buffer_| already
+  // contains unconsumed data. Otherwise, process the data directly from the
+  // source.
+  const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+  const uint8_t* input_end = input + count;
+  if (!input_buffer_.empty()) {
+    input_buffer_.insert(input_buffer_.end(), input, input_end);
+    input = input_buffer_.data();
+    input_end = input + input_buffer_.size();
+  }
+  stream_.next_in = reinterpret_cast<char*>(const_cast<uint8_t*>(input));
+  stream_.avail_in = input_end - input;
+
+  for (;;) {
+    stream_.next_out = reinterpret_cast<char*>(output_buffer.data());
+    stream_.avail_out = output_buffer.size();
+
+    int rc = BZ2_bzDecompress(&stream_);
+    TEST_AND_RETURN_FALSE(rc == BZ_OK || rc == BZ_STREAM_END);
+
+    if (stream_.avail_out == output_buffer.size())
+      break;  // got no new bytes
+
+    TEST_AND_RETURN_FALSE(
+        next_->Write(output_buffer.data(),
+                     output_buffer.size() - stream_.avail_out));
+
+    if (rc == BZ_STREAM_END)
+      CHECK_EQ(stream_.avail_in, 0u);
+    if (stream_.avail_in == 0)
+      break;  // no more input to process
+  }
+
+  // Store unconsumed data (if any) in |input_buffer_|.
+  if (stream_.avail_in || !input_buffer_.empty()) {
+    chromeos::Blob new_input_buffer(input_end - stream_.avail_in, input_end);
+    new_input_buffer.swap(input_buffer_);
+  }
+
+  return true;
+}
+
+bool BzipExtentWriter::EndImpl() {
+  TEST_AND_RETURN_FALSE(input_buffer_.empty());
+  TEST_AND_RETURN_FALSE(BZ2_bzDecompressEnd(&stream_) == BZ_OK);
+  return next_->End();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/bzip_extent_writer.h b/bzip_extent_writer.h
new file mode 100644
index 0000000..6a6133a
--- /dev/null
+++ b/bzip_extent_writer.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2009 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_BZIP_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_BZIP_EXTENT_WRITER_H_
+
+#include <bzlib.h>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/extent_writer.h"
+#include "update_engine/utils.h"
+
+// BzipExtentWriter is a concrete ExtentWriter subclass that bzip-decompresses
+// what it's given in Write. It passes the decompressed data to an underlying
+// ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class BzipExtentWriter : public ExtentWriter {
+ public:
+  explicit BzipExtentWriter(ExtentWriter* next) : next_(next) {
+    memset(&stream_, 0, sizeof(stream_));
+  }
+  ~BzipExtentWriter() {}
+
+  bool Init(FileDescriptorPtr fd,
+            const std::vector<Extent>& extents,
+            uint32_t block_size);
+  bool Write(const void* bytes, size_t count);
+  bool EndImpl();
+
+ private:
+  ExtentWriter* const next_;  // The underlying ExtentWriter.
+  bz_stream stream_;  // the libbz2 stream
+  chromeos::Blob input_buffer_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_BZIP_EXTENT_WRITER_H_
diff --git a/bzip_extent_writer_unittest.cc b/bzip_extent_writer_unittest.cc
new file mode 100644
index 0000000..2b98eb1
--- /dev/null
+++ b/bzip_extent_writer_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2009 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/bzip_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const char kPathTemplate[] = "./BzipExtentWriterTest-file.XXXXXX";
+const uint32_t kBlockSize = 4096;
+}
+
+class BzipExtentWriterTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+    fd_.reset(new EintrSafeFileDescriptor);
+    int fd = mkstemp(path_);
+    ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+    close(fd);
+  }
+  void TearDown() override {
+    fd_->Close();
+    unlink(path_);
+  }
+  void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+  void TestZeroPad(bool aligned_size);
+
+  FileDescriptorPtr fd_;
+  char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(BzipExtentWriterTest, SimpleTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  // 'echo test | bzip2 | hexdump' yields:
+  static const char test_uncompressed[] = "test\n";
+  static const uint8_t test[] = {
+    0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xcc, 0xc3,
+    0x71, 0xd4, 0x00, 0x00, 0x02, 0x41, 0x80, 0x00, 0x10, 0x02, 0x00, 0x0c,
+    0x00, 0x20, 0x00, 0x21, 0x9a, 0x68, 0x33, 0x4d, 0x19, 0x97, 0x8b, 0xb9,
+    0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,
+  };
+
+  DirectExtentWriter direct_writer;
+  BzipExtentWriter bzip_writer(&direct_writer);
+  EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+  EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
+  EXPECT_TRUE(bzip_writer.End());
+
+  chromeos::Blob buf;
+  EXPECT_TRUE(utils::ReadFile(path_, &buf));
+  EXPECT_EQ(strlen(test_uncompressed), buf.size());
+  EXPECT_EQ(string(buf.begin(), buf.end()), string(test_uncompressed));
+}
+
+TEST_F(BzipExtentWriterTest, ChunkedTest) {
+  const chromeos::Blob::size_type kDecompressedLength = 2048 * 1024;  // 2 MiB
+  string decompressed_path;
+  ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-decompressed-XXXXXX",
+                                  &decompressed_path, nullptr));
+  string compressed_path;
+  ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-compressed-XXXXXX",
+                                  &compressed_path, nullptr));
+  const size_t kChunkSize = 3;
+
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(0);
+  extent.set_num_blocks(kDecompressedLength / kBlockSize + 1);
+  extents.push_back(extent);
+
+  chromeos::Blob decompressed_data(kDecompressedLength);
+  test_utils::FillWithData(&decompressed_data);
+
+  EXPECT_TRUE(test_utils::WriteFileVector(
+      decompressed_path, decompressed_data));
+
+  EXPECT_EQ(0, test_utils::System(
+      string("cat ") + decompressed_path + "|bzip2>" + compressed_path));
+
+  chromeos::Blob compressed_data;
+  EXPECT_TRUE(utils::ReadFile(compressed_path, &compressed_data));
+
+  DirectExtentWriter direct_writer;
+  BzipExtentWriter bzip_writer(&direct_writer);
+  EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+
+  chromeos::Blob original_compressed_data = compressed_data;
+  for (chromeos::Blob::size_type i = 0; i < compressed_data.size();
+       i += kChunkSize) {
+    size_t this_chunk_size = min(kChunkSize, compressed_data.size() - i);
+    EXPECT_TRUE(bzip_writer.Write(&compressed_data[i], this_chunk_size));
+  }
+  EXPECT_TRUE(bzip_writer.End());
+
+  // Check that the const input has not been clobbered.
+  test_utils::ExpectVectorsEq(original_compressed_data, compressed_data);
+
+  chromeos::Blob output;
+  EXPECT_TRUE(utils::ReadFile(path_, &output));
+  EXPECT_EQ(kDecompressedLength, output.size());
+  test_utils::ExpectVectorsEq(decompressed_data, output);
+
+  unlink(decompressed_path.c_str());
+  unlink(compressed_path.c_str());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/certificate_checker.cc b/certificate_checker.cc
new file mode 100644
index 0000000..4c1d2ed
--- /dev/null
+++ b/certificate_checker.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 2012 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/certificate_checker.h"
+
+#include <string>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/logging.h>
+#include <curl/curl.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+
+#include "metrics/metrics_library.h"
+#include "update_engine/constants.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+// This should be in the same order of CertificateChecker::ServerToCheck, with
+// the exception of kNone.
+static const char* kReportToSendKey[2] =
+    {kPrefsCertificateReportToSendUpdate,
+     kPrefsCertificateReportToSendDownload};
+}  // namespace
+
+bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+                                          int* out_depth,
+                                          unsigned int* out_digest_length,
+                                          uint8_t* out_digest) const {
+  TEST_AND_RETURN_FALSE(out_digest);
+  X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
+  TEST_AND_RETURN_FALSE(certificate);
+  int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+  if (out_depth)
+    *out_depth = depth;
+
+  unsigned int len;
+  const EVP_MD* digest_function = EVP_sha256();
+  bool success = X509_digest(certificate, digest_function, out_digest, &len);
+
+  if (success && out_digest_length)
+    *out_digest_length = len;
+  return success;
+}
+
+// static
+SystemState* CertificateChecker::system_state_ = nullptr;
+
+// static
+OpenSSLWrapper* CertificateChecker::openssl_wrapper_ = nullptr;
+
+// static
+CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
+                                               SSL_CTX* ssl_ctx,
+                                               void* ptr) {
+  // From here we set the SSL_CTX to another callback, from the openssl library,
+  // which will be called after each server certificate is validated. However,
+  // since openssl does not allow us to pass our own data pointer to the
+  // callback, the certificate check will have to be done statically. Since we
+  // need to know which update server we are using in order to check the
+  // certificate, we hardcode Chrome OS's two known update servers here, and
+  // define a different static callback for each. Since this code should only
+  // run in official builds, this should not be a problem. However, if an update
+  // server different from the ones listed here is used, the check will not
+  // take place.
+  ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
+
+  // We check which server to check and set the appropriate static callback.
+  if (*server_to_check == kUpdate)
+    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackUpdateCheck);
+  if (*server_to_check == kDownload)
+    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, VerifySSLCallbackDownload);
+
+  return CURLE_OK;
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackUpdateCheck(int preverify_ok,
+                                                     X509_STORE_CTX* x509_ctx) {
+  return CertificateChecker::CheckCertificateChange(
+      kUpdate, preverify_ok, x509_ctx) ? 1 : 0;
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
+                                                  X509_STORE_CTX* x509_ctx) {
+  return CertificateChecker::CheckCertificateChange(
+      kDownload, preverify_ok, x509_ctx) ? 1 : 0;
+}
+
+// static
+bool CertificateChecker::CheckCertificateChange(
+    ServerToCheck server_to_check, int preverify_ok,
+    X509_STORE_CTX* x509_ctx) {
+  static const char kUMAActionCertChanged[] =
+      "Updater.ServerCertificateChanged";
+  static const char kUMAActionCertFailed[] = "Updater.ServerCertificateFailed";
+  TEST_AND_RETURN_FALSE(system_state_ != nullptr);
+  TEST_AND_RETURN_FALSE(system_state_->prefs() != nullptr);
+  TEST_AND_RETURN_FALSE(server_to_check != kNone);
+
+  // If pre-verification failed, we are not interested in the current
+  // certificate. We store a report to UMA and just propagate the fail result.
+  if (!preverify_ok) {
+    LOG_IF(WARNING, !system_state_->prefs()->SetString(
+        kReportToSendKey[server_to_check], kUMAActionCertFailed))
+        << "Failed to store UMA report on a failure to validate "
+        << "certificate from update server.";
+    return false;
+  }
+
+  int depth;
+  unsigned int digest_length;
+  uint8_t digest[EVP_MAX_MD_SIZE];
+
+  if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
+                                              &depth,
+                                              &digest_length,
+                                              digest)) {
+    LOG(WARNING) << "Failed to generate digest of X509 certificate "
+                 << "from update server.";
+    return true;
+  }
+
+  // We convert the raw bytes of the digest to an hex string, for storage in
+  // prefs.
+  string digest_string = base::HexEncode(digest, digest_length);
+
+  string storage_key = base::StringPrintf("%s-%d-%d",
+                                          kPrefsUpdateServerCertificate,
+                                          server_to_check,
+                                          depth);
+  string stored_digest;
+  // If there's no stored certificate, we just store the current one and return.
+  if (!system_state_->prefs()->GetString(storage_key, &stored_digest)) {
+    LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
+                                                       digest_string))
+        << "Failed to store server certificate on storage key " << storage_key;
+    return true;
+  }
+
+  // Certificate changed, we store a report to UMA and store the most recent
+  // certificate.
+  if (stored_digest != digest_string) {
+    LOG_IF(WARNING, !system_state_->prefs()->SetString(
+        kReportToSendKey[server_to_check], kUMAActionCertChanged))
+        << "Failed to store UMA report on a change on the "
+        << "certificate from update server.";
+    LOG_IF(WARNING, !system_state_->prefs()->SetString(storage_key,
+                                                       digest_string))
+        << "Failed to store server certificate on storage key " << storage_key;
+  }
+
+  // Since we don't perform actual SSL verification, we return success.
+  return true;
+}
+
+// static
+void CertificateChecker::FlushReport() {
+  // This check shouldn't be needed, but it is useful for testing.
+  TEST_AND_RETURN(system_state_);
+  TEST_AND_RETURN(system_state_->metrics_lib());
+  TEST_AND_RETURN(system_state_->prefs());
+
+  // We flush reports for both servers.
+  for (size_t i = 0; i < arraysize(kReportToSendKey); i++) {
+    string report_to_send;
+    if (system_state_->prefs()->GetString(kReportToSendKey[i], &report_to_send)
+        && !report_to_send.empty()) {
+      // There is a report to be sent. We send it and erase it.
+      LOG(INFO) << "Found report #" << i << ". Sending it";
+      LOG_IF(WARNING, !system_state_->metrics_lib()->SendUserActionToUMA(
+          report_to_send))
+          << "Failed to send server certificate report to UMA: "
+          << report_to_send;
+      LOG_IF(WARNING, !system_state_->prefs()->Delete(kReportToSendKey[i]))
+          << "Failed to erase server certificate report to be sent to UMA";
+    }
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/certificate_checker.h b/certificate_checker.h
new file mode 100644
index 0000000..81e7dde
--- /dev/null
+++ b/certificate_checker.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2011 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_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
+
+#include <curl/curl.h>
+#include <openssl/ssl.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/system_state.h"
+
+namespace chromeos_update_engine {
+
+// Wrapper for openssl operations with the certificates.
+class OpenSSLWrapper {
+ public:
+  OpenSSLWrapper() {}
+  virtual ~OpenSSLWrapper() {}
+
+  // Takes an openssl X509_STORE_CTX, extracts the corresponding certificate
+  // from it and calculates its fingerprint (SHA256 digest). Returns true on
+  // success and false otherwise.
+  //
+  // |x509_ctx| is the pointer to the openssl object that holds the certificate.
+  // |out_depth| is the depth of the current certificate, in the certificate
+  // chain.
+  // |out_digest_length| is the length of the generated digest.
+  // |out_digest| is the byte array where the digest itself will be written.
+  // It should be big enough to hold a SHA1 digest (e.g. EVP_MAX_MD_SIZE).
+  virtual bool GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+                                    int* out_depth,
+                                    unsigned int* out_digest_length,
+                                    uint8_t* out_digest) const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OpenSSLWrapper);
+};
+
+// Responsible for checking whether update server certificates change, and
+// reporting to UMA when this happens. Since all state information is persisted,
+// and openssl forces us to use a static callback with no data pointer, this
+// class is entirely static.
+class CertificateChecker {
+ public:
+  // These values are used to generate the keys of files persisted via prefs.
+  // This means that changing these will cause loss of information on metrics
+  // reporting, during the transition.
+  enum ServerToCheck {
+    kUpdate = 0,
+    kDownload = 1,
+    kNone = 2  // This needs to be the last element. Changing its value is ok.
+  };
+
+  CertificateChecker() {}
+  virtual ~CertificateChecker() {}
+
+  // This callback is called by libcurl just before the initialization of an
+  // SSL connection after having processed all other SSL related options. Used
+  // to check if server certificates change. |ptr| is expected to be a
+  // pointer to a ServerToCheck.
+  static CURLcode ProcessSSLContext(CURL* curl_handle, SSL_CTX* ssl_ctx,
+                                    void* ptr);
+
+  // Flushes to UMA any certificate-related report that was persisted.
+  static void FlushReport();
+
+  // Setters.
+  static void set_system_state(SystemState* system_state) {
+    system_state_ = system_state;
+  }
+
+  static void set_openssl_wrapper(OpenSSLWrapper* openssl_wrapper) {
+    openssl_wrapper_ = openssl_wrapper;
+  }
+
+ private:
+  FRIEND_TEST(CertificateCheckerTest, NewCertificate);
+  FRIEND_TEST(CertificateCheckerTest, SameCertificate);
+  FRIEND_TEST(CertificateCheckerTest, ChangedCertificate);
+  FRIEND_TEST(CertificateCheckerTest, FailedCertificate);
+  FRIEND_TEST(CertificateCheckerTest, FlushReport);
+  FRIEND_TEST(CertificateCheckerTest, FlushNothingToReport);
+
+  // These callbacks are called by openssl after initial SSL verification. They
+  // are used to perform any additional security verification on the connection,
+  // but we use them here to get hold of the server certificate, in order to
+  // determine if it has changed since the last connection. Since openssl forces
+  // us to do this statically, we define two different callbacks for the two
+  // different official update servers, and only assign the correspondent one.
+  // The assigned callback is then called once per each certificate on the
+  // server and returns 1 for success and 0 for failure.
+  static int VerifySSLCallbackUpdateCheck(int preverify_ok,
+                                          X509_STORE_CTX* x509_ctx);
+  static int VerifySSLCallbackDownload(int preverify_ok,
+                                       X509_STORE_CTX* x509_ctx);
+
+  // Checks if server certificate for |server_to_check|, stored in |x509_ctx|,
+  // has changed since last connection to that same server. This is called by
+  // one of the two callbacks defined above. If certificate fails to check or
+  // changes, a report is generated and persisted, to be later sent by
+  // FlushReport. Returns true on success and false otherwise.
+  static bool CheckCertificateChange(ServerToCheck server_to_check,
+                                     int preverify_ok,
+                                     X509_STORE_CTX* x509_ctx);
+
+  // Global system context.
+  static SystemState* system_state_;
+
+  // The wrapper for openssl operations.
+  static OpenSSLWrapper* openssl_wrapper_;
+
+  DISALLOW_COPY_AND_ASSIGN(CertificateChecker);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CERTIFICATE_CHECKER_H_
diff --git a/certificate_checker_unittest.cc b/certificate_checker_unittest.cc
new file mode 100644
index 0000000..5d4d8f0
--- /dev/null
+++ b/certificate_checker_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright (c) 2012 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/certificate_checker.h"
+
+#include <string>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <metrics/metrics_library_mock.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_certificate_checker.h"
+#include "update_engine/mock_prefs.h"
+
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::SetArrayArgument;
+using ::testing::_;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class CertificateCheckerTest : public testing::Test {
+ public:
+  CertificateCheckerTest() {}
+
+ protected:
+  void SetUp() override {
+    depth_ = 0;
+    length_ = 4;
+    digest_[0] = 0x17;
+    digest_[1] = 0x7D;
+    digest_[2] = 0x07;
+    digest_[3] = 0x5F;
+    digest_hex_ = "177D075F";
+    diff_digest_hex_ = "1234ABCD";
+    cert_key_prefix_ = kPrefsUpdateServerCertificate;
+    server_to_check_ = CertificateChecker::kUpdate;
+    cert_key_ = base::StringPrintf("%s-%d-%d",
+                                   cert_key_prefix_.c_str(),
+                                   server_to_check_,
+                                   depth_);
+    kCertChanged = "Updater.ServerCertificateChanged";
+    kCertFailed = "Updater.ServerCertificateFailed";
+    CertificateChecker::set_system_state(&fake_system_state_);
+    CertificateChecker::set_openssl_wrapper(&openssl_wrapper_);
+    prefs_ = fake_system_state_.mock_prefs();
+  }
+
+  void TearDown() override {}
+
+  FakeSystemState fake_system_state_;
+  MockPrefs* prefs_;  // shortcut to fake_system_state_.mock_prefs()
+  MockOpenSSLWrapper openssl_wrapper_;
+  // Parameters of our mock certificate digest.
+  int depth_;
+  unsigned int length_;
+  uint8_t digest_[4];
+  string digest_hex_;
+  string diff_digest_hex_;
+  string cert_key_prefix_;
+  CertificateChecker::ServerToCheck server_to_check_;
+  string cert_key_;
+  string kCertChanged;
+  string kCertFailed;
+};
+
+// check certificate change, new
+TEST_F(CertificateCheckerTest, NewCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(*prefs_, GetString(cert_key_, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(*prefs_, SetString(cert_key_, digest_hex_))
+      .WillOnce(Return(true));
+  ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 1, nullptr));
+}
+
+// check certificate change, unchanged
+TEST_F(CertificateCheckerTest, SameCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(*prefs_, GetString(cert_key_, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(digest_hex_),
+          Return(true)));
+  EXPECT_CALL(*prefs_, SetString(_, _)).Times(0);
+  ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 1, nullptr));
+}
+
+// check certificate change, changed
+TEST_F(CertificateCheckerTest, ChangedCertificate) {
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(depth_),
+          SetArgumentPointee<2>(length_),
+          SetArrayArgument<3>(digest_, digest_ + 4),
+          Return(true)));
+  EXPECT_CALL(*prefs_, GetString(cert_key_, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(diff_digest_hex_),
+          Return(true)));
+  EXPECT_CALL(*prefs_, SetString(kPrefsCertificateReportToSendUpdate,
+                                kCertChanged))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs_, SetString(cert_key_, digest_hex_))
+      .WillOnce(Return(true));
+  ASSERT_TRUE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 1, nullptr));
+}
+
+// check certificate change, failed
+TEST_F(CertificateCheckerTest, FailedCertificate) {
+  EXPECT_CALL(*prefs_, SetString(kPrefsCertificateReportToSendUpdate,
+                                kCertFailed))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs_, GetString(_, _)).Times(0);
+  EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(_, _, _, _)).Times(0);
+  ASSERT_FALSE(CertificateChecker::CheckCertificateChange(
+      server_to_check_, 0, nullptr));
+}
+
+// flush send report
+TEST_F(CertificateCheckerTest, FlushReport) {
+  EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendUpdate, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(kCertChanged),
+          Return(true)));
+  EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendDownload, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+              SendUserActionToUMA(kCertChanged))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs_, Delete(kPrefsCertificateReportToSendUpdate))
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs_, SetString(kPrefsCertificateReportToSendDownload, _))
+      .Times(0);
+  CertificateChecker::FlushReport();
+}
+
+// flush nothing to report
+TEST_F(CertificateCheckerTest, FlushNothingToReport) {
+  string empty = "";
+  EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendUpdate, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(empty),
+          Return(true)));
+  EXPECT_CALL(*prefs_, GetString(kPrefsCertificateReportToSendDownload, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+              SendUserActionToUMA(_)).Times(0);
+  EXPECT_CALL(*prefs_, SetString(_, _)).Times(0);
+  CertificateChecker::FlushReport();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/chrome_browser_proxy_resolver.cc b/chrome_browser_proxy_resolver.cc
new file mode 100644
index 0000000..116c67a
--- /dev/null
+++ b/chrome_browser_proxy_resolver.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2011 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/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <dbus/dbus-glib.h>
+
+#include "update_engine/dbus_constants.h"
+#include "update_engine/glib_utils.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using base::StringTokenizer;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::deque;
+using std::make_pair;
+using std::pair;
+using std::string;
+
+#define LIB_CROS_PROXY_RESOLVE_NAME "ProxyResolved"
+#define LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE                 \
+  "org.chromium.UpdateEngineLibcrosProxyResolvedInterface"
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+const char kLibCrosServicePath[] = "/org/chromium/LibCrosService";
+const char kLibCrosServiceInterface[] = "org.chromium.LibCrosServiceInterface";
+const char kLibCrosServiceResolveNetworkProxyMethodName[] =
+    "ResolveNetworkProxy";
+const char kLibCrosProxyResolveName[] = LIB_CROS_PROXY_RESOLVE_NAME;
+const char kLibCrosProxyResolveSignalInterface[] =
+    LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE;
+const char kLibCrosProxyResolveSignalFilter[] = "type='signal', "
+    "interface='" LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE "', "
+    "member='" LIB_CROS_PROXY_RESOLVE_NAME "'";
+#undef LIB_CROS_PROXY_RESOLVE_SIGNAL_INTERFACE
+#undef LIB_CROS_PROXY_RESOLVE_NAME
+
+namespace {
+
+const int kTimeout = 5;  // seconds
+
+}  // namespace
+
+ChromeBrowserProxyResolver::ChromeBrowserProxyResolver(
+    DBusWrapperInterface* dbus)
+    : dbus_(dbus), timeout_(kTimeout) {}
+
+bool ChromeBrowserProxyResolver::Init() {
+  if (proxy_)
+    return true;  // Already initialized.
+
+  // Set up signal handler. Code lifted from libcros.
+  GError* g_error = nullptr;
+  DBusGConnection* bus = dbus_->BusGet(DBUS_BUS_SYSTEM, &g_error);
+  TEST_AND_RETURN_FALSE(bus);
+  DBusConnection* connection = dbus_->ConnectionGetConnection(bus);
+  TEST_AND_RETURN_FALSE(connection);
+
+  DBusError dbus_error;
+  dbus_error_init(&dbus_error);
+  dbus_->DBusBusAddMatch(connection, kLibCrosProxyResolveSignalFilter,
+                         &dbus_error);
+  TEST_AND_RETURN_FALSE(!dbus_error_is_set(&dbus_error));
+  TEST_AND_RETURN_FALSE(dbus_->DBusConnectionAddFilter(
+      connection,
+      &ChromeBrowserProxyResolver::StaticFilterMessage,
+      this,
+      nullptr));
+
+  proxy_ = dbus_->ProxyNewForName(bus, kLibCrosServiceName, kLibCrosServicePath,
+                                  kLibCrosServiceInterface);
+  if (!proxy_) {
+    dbus_->DBusConnectionRemoveFilter(
+        connection,
+        &ChromeBrowserProxyResolver::StaticFilterMessage,
+        this);
+  }
+  TEST_AND_RETURN_FALSE(proxy_);  // For the error log
+  return true;
+}
+
+ChromeBrowserProxyResolver::~ChromeBrowserProxyResolver() {
+  // Remove DBus connection filters and Kill proxy object.
+  if (proxy_) {
+    GError* gerror = nullptr;
+    DBusGConnection* gbus = dbus_->BusGet(DBUS_BUS_SYSTEM, &gerror);
+    if (gbus) {
+      DBusConnection* connection = dbus_->ConnectionGetConnection(gbus);
+      dbus_->DBusConnectionRemoveFilter(
+          connection,
+          &ChromeBrowserProxyResolver::StaticFilterMessage,
+          this);
+    }
+    dbus_->ProxyUnref(proxy_);
+  }
+
+  // Kill outstanding timers
+  for (auto& timer : timers_) {
+    MessageLoop::current()->CancelTask(timer.second);
+    timer.second = MessageLoop::kTaskIdNull;
+  }
+}
+
+bool ChromeBrowserProxyResolver::GetProxiesForUrl(const string& url,
+                                                  ProxiesResolvedFn callback,
+                                                  void* data) {
+  GError* error = nullptr;
+  guint timeout = timeout_;
+  if (proxy_) {
+    if (!dbus_->ProxyCall_3_0(proxy_,
+                              kLibCrosServiceResolveNetworkProxyMethodName,
+                              &error,
+                              url.c_str(),
+                              kLibCrosProxyResolveSignalInterface,
+                              kLibCrosProxyResolveName)) {
+      if (error) {
+        LOG(WARNING) << "dbus_g_proxy_call failed, continuing with no proxy: "
+                     << utils::GetAndFreeGError(&error);
+      } else {
+        LOG(WARNING) << "dbus_g_proxy_call failed with no error string, "
+                        "continuing with no proxy.";
+      }
+      timeout = 0;
+    }
+  } else {
+    LOG(WARNING) << "dbus proxy object missing, continuing with no proxy.";
+    timeout = 0;
+  }
+
+  callbacks_.insert(make_pair(url, make_pair(callback, data)));
+  MessageLoop::TaskId timer = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&ChromeBrowserProxyResolver::HandleTimeout,
+                 base::Unretained(this),
+                 url),
+      TimeDelta::FromSeconds(timeout));
+  timers_.insert(make_pair(url, timer));
+  return true;
+}
+
+DBusHandlerResult ChromeBrowserProxyResolver::FilterMessage(
+    DBusConnection* connection,
+    DBusMessage* message) {
+  // Code lifted from libcros.
+  if (!dbus_->DBusMessageIsSignal(message,
+                                  kLibCrosProxyResolveSignalInterface,
+                                  kLibCrosProxyResolveName)) {
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+  }
+  // Get args
+  char* source_url = nullptr;
+  char* proxy_list = nullptr;
+  char* error = nullptr;
+  DBusError arg_error;
+  dbus_error_init(&arg_error);
+  if (!dbus_->DBusMessageGetArgs_3(message, &arg_error,
+                                   &source_url,
+                                   &proxy_list,
+                                   &error)) {
+    LOG(ERROR) << "Error reading dbus signal.";
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+  if (!source_url || !proxy_list) {
+    LOG(ERROR) << "Error getting url, proxy list from dbus signal.";
+    return DBUS_HANDLER_RESULT_HANDLED;
+  }
+  HandleReply(source_url, proxy_list);
+  return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+bool ChromeBrowserProxyResolver::DeleteUrlState(
+    const string& source_url,
+    bool delete_timer,
+    pair<ProxiesResolvedFn, void*>* callback) {
+  {
+    CallbacksMap::iterator it = callbacks_.lower_bound(source_url);
+    TEST_AND_RETURN_FALSE(it != callbacks_.end());
+    TEST_AND_RETURN_FALSE(it->first == source_url);
+    if (callback)
+      *callback = it->second;
+    callbacks_.erase(it);
+  }
+  {
+    TimeoutsMap::iterator it = timers_.lower_bound(source_url);
+    TEST_AND_RETURN_FALSE(it != timers_.end());
+    TEST_AND_RETURN_FALSE(it->first == source_url);
+    if (delete_timer)
+      MessageLoop::current()->CancelTask(it->second);
+    timers_.erase(it);
+  }
+  return true;
+}
+
+void ChromeBrowserProxyResolver::HandleReply(const string& source_url,
+                                             const string& proxy_list) {
+  pair<ProxiesResolvedFn, void*> callback;
+  TEST_AND_RETURN(DeleteUrlState(source_url, true, &callback));
+  (*callback.first)(ParseProxyString(proxy_list), callback.second);
+}
+
+void ChromeBrowserProxyResolver::HandleTimeout(string source_url) {
+  LOG(INFO) << "Timeout handler called. Seems Chrome isn't responding.";
+  pair<ProxiesResolvedFn, void*> callback;
+  TEST_AND_RETURN(DeleteUrlState(source_url, false, &callback));
+  deque<string> proxies;
+  proxies.push_back(kNoProxy);
+  (*callback.first)(proxies, callback.second);
+}
+
+deque<string> ChromeBrowserProxyResolver::ParseProxyString(
+    const string& input) {
+  deque<string> ret;
+  // Some of this code taken from
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
+  StringTokenizer entry_tok(input, ";");
+  while (entry_tok.GetNext()) {
+    string token = entry_tok.token();
+    base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
+
+    // Start by finding the first space (if any).
+    string::iterator space;
+    for (space = token.begin(); space != token.end(); ++space) {
+      if (IsAsciiWhitespace(*space)) {
+        break;
+      }
+    }
+
+    string scheme = string(token.begin(), space);
+    base::StringToLowerASCII(&scheme);
+    // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
+    if (scheme == "socks")
+      scheme += "4";
+    else if (scheme == "proxy")
+      scheme = "http";
+    else if (scheme != "https" &&
+             scheme != "socks4" &&
+             scheme != "socks5" &&
+             scheme != "direct")
+      continue;  // Invalid proxy scheme
+
+    string host_and_port = string(space, token.end());
+    base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
+    if (scheme != "direct" && host_and_port.empty())
+      continue;  // Must supply host/port when non-direct proxy used.
+    ret.push_back(scheme + "://" + host_and_port);
+  }
+  if (ret.empty() || *ret.rbegin() != kNoProxy)
+    ret.push_back(kNoProxy);
+  return ret;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/chrome_browser_proxy_resolver.h b/chrome_browser_proxy_resolver.h
new file mode 100644
index 0000000..ab0a92c
--- /dev/null
+++ b/chrome_browser_proxy_resolver.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2011 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_CHROME_BROWSER_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/proxy_resolver.h"
+
+namespace chromeos_update_engine {
+
+extern const char kLibCrosServiceName[];
+extern const char kLibCrosServicePath[];
+extern const char kLibCrosServiceInterface[];
+extern const char kLibCrosServiceResolveNetworkProxyMethodName[];
+extern const char kLibCrosProxyResolveName[];
+extern const char kLibCrosProxyResolveSignalInterface[];
+extern const char kLibCrosProxyResolveSignalFilter[];
+
+class ChromeBrowserProxyResolver : public ProxyResolver {
+ public:
+  explicit ChromeBrowserProxyResolver(DBusWrapperInterface* dbus);
+  ~ChromeBrowserProxyResolver() override;
+  bool Init();
+
+  bool GetProxiesForUrl(const std::string& url,
+                        ProxiesResolvedFn callback,
+                        void* data) override;
+  void set_timeout(int seconds) { timeout_ = seconds; }
+
+  // Public for test
+  static DBusHandlerResult StaticFilterMessage(
+      DBusConnection* connection,
+      DBusMessage* message,
+      void* data) {
+    return reinterpret_cast<ChromeBrowserProxyResolver*>(data)->FilterMessage(
+        connection, message);
+  }
+
+ private:
+  FRIEND_TEST(ChromeBrowserProxyResolverTest, ParseTest);
+  FRIEND_TEST(ChromeBrowserProxyResolverTest, SuccessTest);
+  typedef std::multimap<std::string, std::pair<ProxiesResolvedFn, void*>>
+      CallbacksMap;
+  typedef std::multimap<std::string, chromeos::MessageLoop::TaskId> TimeoutsMap;
+
+  // Handle a reply from Chrome:
+  void HandleReply(const std::string& source_url,
+                   const std::string& proxy_list);
+  DBusHandlerResult FilterMessage(
+      DBusConnection* connection,
+      DBusMessage* message);
+  // Handle no reply:
+  void HandleTimeout(std::string source_url);
+
+  // Parses a string-encoded list of proxies and returns a deque
+  // of individual proxies. The last one will always be kNoProxy.
+  static std::deque<std::string> ParseProxyString(const std::string& input);
+
+  // Deletes internal state for the first instance of url in the state.
+  // If delete_timer is set, calls CancelTask on the timer id.
+  // Returns the callback in an out parameter. Returns true on success.
+  bool DeleteUrlState(const std::string& url,
+                      bool delete_timer,
+                      std::pair<ProxiesResolvedFn, void*>* callback);
+
+  // Shutdown the dbus proxy object.
+  void Shutdown();
+
+  DBusWrapperInterface* dbus_;
+  DBusGProxy* proxy_{nullptr};
+  DBusGProxy* peer_proxy_{nullptr};
+  int timeout_;
+  TimeoutsMap timers_;
+  CallbacksMap callbacks_;
+  DISALLOW_COPY_AND_ASSIGN(ChromeBrowserProxyResolver);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
diff --git a/chrome_browser_proxy_resolver_unittest.cc b/chrome_browser_proxy_resolver_unittest.cc
new file mode 100644
index 0000000..3866358
--- /dev/null
+++ b/chrome_browser_proxy_resolver_unittest.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 2011 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/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <base/bind.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+
+#include "update_engine/mock_dbus_wrapper.h"
+
+using ::testing::Return;
+using ::testing::SetArgPointee;
+using ::testing::StrEq;
+using ::testing::_;
+using chromeos::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class ChromeBrowserProxyResolverTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+ private:
+  chromeos::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(ChromeBrowserProxyResolverTest, ParseTest) {
+  // Test ideas from
+  // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list_unittest.cc
+  const char* inputs[] = {
+    "PROXY foopy:10",
+    " DIRECT",  // leading space.
+    "PROXY foopy1 ; proxy foopy2;\t DIRECT",
+    "proxy foopy1 ; SOCKS foopy2",
+    "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ",
+    "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT",
+    "PROXY-foopy:10",
+    "PROXY",
+    "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;",
+    "HTTP foopy1; SOCKS5 foopy2"
+  };
+  deque<string> outputs[arraysize(inputs)];
+  outputs[0].push_back("http://foopy:10");
+  outputs[0].push_back(kNoProxy);
+  outputs[1].push_back(kNoProxy);
+  outputs[2].push_back("http://foopy1");
+  outputs[2].push_back("http://foopy2");
+  outputs[2].push_back(kNoProxy);
+  outputs[3].push_back("http://foopy1");
+  outputs[3].push_back("socks4://foopy2");
+  outputs[3].push_back(kNoProxy);
+  outputs[4].push_back(kNoProxy);
+  outputs[4].push_back("http://foopy1");
+  outputs[4].push_back(kNoProxy);
+  outputs[4].push_back("socks5://foopy2");
+  outputs[4].push_back(kNoProxy);
+  outputs[5].push_back(kNoProxy);
+  outputs[5].push_back("http://foopy1:80");
+  outputs[5].push_back(kNoProxy);
+  outputs[5].push_back(kNoProxy);
+  outputs[6].push_back(kNoProxy);
+  outputs[7].push_back(kNoProxy);
+  outputs[8].push_back("http://foopy1");
+  outputs[8].push_back("socks5://foopy2");
+  outputs[8].push_back(kNoProxy);
+  outputs[9].push_back("socks5://foopy2");
+  outputs[9].push_back(kNoProxy);
+
+  for (size_t i = 0; i < arraysize(inputs); i++) {
+    deque<string> results =
+        ChromeBrowserProxyResolver::ParseProxyString(inputs[i]);
+    deque<string>& expected = outputs[i];
+    EXPECT_EQ(results.size(), expected.size()) << "i = " << i;
+    if (expected.size() != results.size())
+      continue;
+    for (size_t j = 0; j < expected.size(); j++) {
+      EXPECT_EQ(expected[j], results[j]) << "i = " << i;
+    }
+  }
+}
+
+namespace {
+void DBusWrapperTestResolved(const deque<string>& proxies,
+                             void* /* pirv_data */) {
+  EXPECT_EQ(2, proxies.size());
+  EXPECT_EQ("socks5://192.168.52.83:5555", proxies[0]);
+  EXPECT_EQ(kNoProxy, proxies[1]);
+  MessageLoop::current()->BreakLoop();
+}
+void DBusWrapperTestResolvedNoReply(const deque<string>& proxies,
+                                    void* /* pirv_data */) {
+  EXPECT_EQ(1, proxies.size());
+  EXPECT_EQ(kNoProxy, proxies[0]);
+  MessageLoop::current()->BreakLoop();
+}
+
+void SendReply(DBusConnection* connection,
+               DBusMessage* message,
+               ChromeBrowserProxyResolver* resolver) {
+  LOG(INFO) << "Calling SendReply";
+  ChromeBrowserProxyResolver::StaticFilterMessage(connection,
+                                                  message,
+                                                  resolver);
+}
+
+// chrome_replies should be set to whether or not we fake a reply from
+// chrome. If there's no reply, the resolver should time out.
+// If chrome_alive is false, assume that sending to chrome fails.
+void RunTest(bool chrome_replies, bool chrome_alive) {
+  intptr_t number = 1;
+  DBusGConnection* kMockSystemGBus =
+      reinterpret_cast<DBusGConnection*>(number++);
+  DBusConnection* kMockSystemBus =
+      reinterpret_cast<DBusConnection*>(number++);
+  DBusGProxy* kMockDbusProxy =
+      reinterpret_cast<DBusGProxy*>(number++);
+  DBusMessage* kMockDbusMessage =
+      reinterpret_cast<DBusMessage*>(number++);
+
+  char kUrl[] = "http://example.com/blah";
+  char kProxyConfig[] = "SOCKS5 192.168.52.83:5555;DIRECT";
+
+  testing::StrictMock<MockDBusWrapper> dbus_iface;
+
+  EXPECT_CALL(dbus_iface, BusGet(_, _))
+      .Times(2)
+      .WillRepeatedly(Return(kMockSystemGBus));
+  EXPECT_CALL(dbus_iface,
+              ConnectionGetConnection(kMockSystemGBus))
+      .Times(2)
+      .WillRepeatedly(Return(kMockSystemBus));
+  EXPECT_CALL(dbus_iface, DBusBusAddMatch(kMockSystemBus, _, _));
+  EXPECT_CALL(dbus_iface,
+              DBusConnectionAddFilter(kMockSystemBus, _, _, _))
+      .WillOnce(Return(1));
+  EXPECT_CALL(dbus_iface,
+              ProxyNewForName(kMockSystemGBus,
+                              StrEq(kLibCrosServiceName),
+                              StrEq(kLibCrosServicePath),
+                              StrEq(kLibCrosServiceInterface)))
+      .WillOnce(Return(kMockDbusProxy));
+  EXPECT_CALL(dbus_iface, ProxyUnref(kMockDbusProxy));
+
+  EXPECT_CALL(dbus_iface, ProxyCall_3_0(
+      kMockDbusProxy,
+      StrEq(kLibCrosServiceResolveNetworkProxyMethodName),
+      _,
+      StrEq(kUrl),
+      StrEq(kLibCrosProxyResolveSignalInterface),
+      StrEq(kLibCrosProxyResolveName)))
+      .WillOnce(Return(chrome_alive ? TRUE : FALSE));
+
+  EXPECT_CALL(dbus_iface,
+              DBusConnectionRemoveFilter(kMockSystemBus, _, _));
+
+  if (chrome_replies) {
+    EXPECT_CALL(dbus_iface,
+                DBusMessageIsSignal(kMockDbusMessage,
+                                    kLibCrosProxyResolveSignalInterface,
+                                    kLibCrosProxyResolveName))
+        .WillOnce(Return(1));
+    EXPECT_CALL(dbus_iface,
+                DBusMessageGetArgs_3(kMockDbusMessage, _, _, _, _))
+        .WillOnce(DoAll(SetArgPointee<2>(static_cast<char*>(kUrl)),
+                        SetArgPointee<3>(static_cast<char*>(kProxyConfig)),
+                        Return(TRUE)));
+  }
+
+  ChromeBrowserProxyResolver resolver(&dbus_iface);
+  EXPECT_EQ(true, resolver.Init());
+  resolver.set_timeout(1);
+  ProxiesResolvedFn get_proxies_response = &DBusWrapperTestResolvedNoReply;
+
+  if (chrome_replies) {
+    get_proxies_response = &DBusWrapperTestResolved;
+    MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(&SendReply, kMockSystemBus, kMockDbusMessage, &resolver));
+  }
+
+  EXPECT_TRUE(resolver.GetProxiesForUrl(kUrl, get_proxies_response, nullptr));
+  MessageLoop::current()->Run();
+}
+}  // namespace
+
+TEST_F(ChromeBrowserProxyResolverTest, SuccessTest) {
+  RunTest(true, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoReplyTest) {
+  RunTest(false, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoChromeTest) {
+  RunTest(false, false);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/clock.cc b/clock.cc
new file mode 100644
index 0000000..ac8945b
--- /dev/null
+++ b/clock.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2013 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/clock.h"
+
+#include <time.h>
+
+namespace chromeos_update_engine {
+
+base::Time Clock::GetWallclockTime() {
+  return base::Time::Now();
+}
+
+base::Time Clock::GetMonotonicTime() {
+  struct timespec now_ts;
+  if (clock_gettime(CLOCK_MONOTONIC_RAW, &now_ts) != 0) {
+    // Avoid logging this as an error as call-sites may call this very
+    // often and we don't want to fill up the disk. Note that this
+    // only fails if running on ancient kernels (CLOCK_MONOTONIC_RAW
+    // was added in Linux 2.6.28) so it never fails on a ChromeOS
+    // device.
+    return base::Time();
+  }
+  struct timeval now_tv;
+  now_tv.tv_sec = now_ts.tv_sec;
+  now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+  return base::Time::FromTimeVal(now_tv);
+}
+
+base::Time Clock::GetBootTime() {
+  struct timespec now_ts;
+  if (clock_gettime(CLOCK_BOOTTIME, &now_ts) != 0) {
+    // Avoid logging this as an error as call-sites may call this very
+    // often and we don't want to fill up the disk. Note that this
+    // only fails if running on ancient kernels (CLOCK_BOOTTIME was
+    // added in Linux 2.6.39) so it never fails on a ChromeOS device.
+    return base::Time();
+  }
+  struct timeval now_tv;
+  now_tv.tv_sec = now_ts.tv_sec;
+  now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+  return base::Time::FromTimeVal(now_tv);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/clock.h b/clock.h
new file mode 100644
index 0000000..d53ad34
--- /dev/null
+++ b/clock.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 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_CLOCK_H_
+#define UPDATE_ENGINE_CLOCK_H_
+
+#include "update_engine/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock.
+class Clock : public ClockInterface {
+ public:
+  Clock() {}
+
+  base::Time GetWallclockTime() override;
+
+  base::Time GetMonotonicTime() override;
+
+  base::Time GetBootTime() override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Clock);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CLOCK_H_
diff --git a/clock_interface.h b/clock_interface.h
new file mode 100644
index 0000000..8ff122c
--- /dev/null
+++ b/clock_interface.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 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_CLOCK_INTERFACE_H_
+#define UPDATE_ENGINE_CLOCK_INTERFACE_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The clock interface allows access to various system clocks. The
+// sole reason for providing this as an interface is unit testing.
+// Additionally, the sole reason for the methods not being static
+// is also unit testing.
+class ClockInterface {
+ public:
+  virtual ~ClockInterface() = default;
+
+  // Gets the current time e.g. similar to base::Time::Now().
+  virtual base::Time GetWallclockTime() = 0;
+
+  // Returns monotonic time since some unspecified starting point. It
+  // is not increased when the system is sleeping nor is it affected
+  // by NTP or the user changing the time.
+  //
+  // (This is a simple wrapper around clock_gettime(2) / CLOCK_MONOTONIC_RAW.)
+  virtual base::Time GetMonotonicTime() = 0;
+
+  // Returns monotonic time since some unspecified starting point. It
+  // is increased when the system is sleeping but it's not affected
+  // by NTP or the user changing the time.
+  //
+  // (This is a simple wrapper around clock_gettime(2) / CLOCK_BOOTTIME.)
+  virtual base::Time GetBootTime() = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CLOCK_INTERFACE_H_
diff --git a/connection_manager.cc b/connection_manager.cc
new file mode 100644
index 0000000..f6c0217
--- /dev/null
+++ b/connection_manager.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2012 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/connection_manager.h"
+
+#include <set>
+#include <string>
+
+#include <base/stl_util.h>
+#include <base/strings/string_util.h>
+#include <chromeos/dbus/service_constants.h>
+#include <dbus/dbus-glib.h>
+#include <glib.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/prefs.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Gets the DbusGProxy for FlimFlam. Must be free'd with ProxyUnref()
+bool GetFlimFlamProxy(DBusWrapperInterface* dbus_iface,
+                      const char* path,
+                      const char* interface,
+                      DBusGProxy** out_proxy) {
+  DBusGConnection* bus;
+  DBusGProxy* proxy;
+  GError* error = nullptr;
+
+  bus = dbus_iface->BusGet(DBUS_BUS_SYSTEM, &error);
+  if (!bus) {
+    LOG(ERROR) << "Failed to get system bus";
+    return false;
+  }
+  proxy = dbus_iface->ProxyNewForName(bus, shill::kFlimflamServiceName, path,
+                                      interface);
+  *out_proxy = proxy;
+  return true;
+}
+
+// On success, caller owns the GHashTable at out_hash_table.
+// Returns true on success.
+bool GetProperties(DBusWrapperInterface* dbus_iface,
+                   const char* path,
+                   const char* interface,
+                   GHashTable** out_hash_table) {
+  DBusGProxy* proxy;
+  GError* error = nullptr;
+
+  TEST_AND_RETURN_FALSE(GetFlimFlamProxy(dbus_iface,
+                                         path,
+                                         interface,
+                                         &proxy));
+
+  gboolean rc = dbus_iface->ProxyCall_0_1(proxy,
+                                          "GetProperties",
+                                          &error,
+                                          out_hash_table);
+  dbus_iface->ProxyUnref(proxy);
+  if (rc == FALSE) {
+    LOG(ERROR) << "dbus_g_proxy_call failed";
+    return false;
+  }
+
+  return true;
+}
+
+// Returns (via out_path) the default network path, or empty string if
+// there's no network up.
+// Returns true on success.
+bool GetDefaultServicePath(DBusWrapperInterface* dbus_iface, string* out_path) {
+  GHashTable* hash_table = nullptr;
+
+  TEST_AND_RETURN_FALSE(GetProperties(dbus_iface,
+                                      shill::kFlimflamServicePath,
+                                      shill::kFlimflamManagerInterface,
+                                      &hash_table));
+
+  GValue* value = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table,
+                                                                "Services"));
+  GPtrArray* array = nullptr;
+  bool success = false;
+  if (G_VALUE_HOLDS(value, DBUS_TYPE_G_OBJECT_PATH_ARRAY) &&
+      (array = reinterpret_cast<GPtrArray*>(g_value_get_boxed(value))) &&
+      (array->len > 0)) {
+    *out_path = static_cast<const char*>(g_ptr_array_index(array, 0));
+    success = true;
+  }
+
+  g_hash_table_unref(hash_table);
+  return success;
+}
+
+NetworkConnectionType ParseConnectionType(const char* type_str) {
+  if (!strcmp(type_str, shill::kTypeEthernet)) {
+    return kNetEthernet;
+  } else if (!strcmp(type_str, shill::kTypeWifi)) {
+    return kNetWifi;
+  } else if (!strcmp(type_str, shill::kTypeWimax)) {
+    return kNetWimax;
+  } else if (!strcmp(type_str, shill::kTypeBluetooth)) {
+    return kNetBluetooth;
+  } else if (!strcmp(type_str, shill::kTypeCellular)) {
+    return kNetCellular;
+  }
+  return kNetUnknown;
+}
+
+NetworkTethering ParseTethering(const char* tethering_str) {
+  if (!strcmp(tethering_str, shill::kTetheringNotDetectedState)) {
+    return NetworkTethering::kNotDetected;
+  } else if (!strcmp(tethering_str, shill::kTetheringSuspectedState)) {
+    return NetworkTethering::kSuspected;
+  } else if (!strcmp(tethering_str, shill::kTetheringConfirmedState)) {
+    return NetworkTethering::kConfirmed;
+  }
+  LOG(WARNING) << "Unknown Tethering value: " << tethering_str;
+  return NetworkTethering::kUnknown;
+}
+
+bool GetServicePathProperties(DBusWrapperInterface* dbus_iface,
+                              const string& path,
+                              NetworkConnectionType* out_type,
+                              NetworkTethering* out_tethering) {
+  GHashTable* hash_table = nullptr;
+
+  TEST_AND_RETURN_FALSE(GetProperties(dbus_iface,
+                                      path.c_str(),
+                                      shill::kFlimflamServiceInterface,
+                                      &hash_table));
+
+  // Populate the out_tethering.
+  GValue* value =
+      reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table,
+                                                    shill::kTetheringProperty));
+  const char* tethering_str = nullptr;
+
+  if (value != nullptr)
+    tethering_str = g_value_get_string(value);
+  if (tethering_str != nullptr) {
+    *out_tethering = ParseTethering(tethering_str);
+  } else {
+    // Set to Unknown if not present.
+    *out_tethering = NetworkTethering::kUnknown;
+  }
+
+  // Populate the out_type property.
+  value = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table,
+                                                        shill::kTypeProperty));
+  const char* type_str = nullptr;
+  bool success = false;
+  if (value != nullptr && (type_str = g_value_get_string(value)) != nullptr) {
+    success = true;
+    if (!strcmp(type_str, shill::kTypeVPN)) {
+      value = reinterpret_cast<GValue*>(
+          g_hash_table_lookup(hash_table, shill::kPhysicalTechnologyProperty));
+      if (value != nullptr &&
+          (type_str = g_value_get_string(value)) != nullptr) {
+        *out_type = ParseConnectionType(type_str);
+      } else {
+        LOG(ERROR) << "No PhysicalTechnology property found for a VPN"
+                   << " connection (service: " << path << "). Returning default"
+                   << " kNetUnknown value.";
+        *out_type = kNetUnknown;
+      }
+    } else {
+      *out_type = ParseConnectionType(type_str);
+    }
+  }
+  g_hash_table_unref(hash_table);
+  return success;
+}
+
+}  // namespace
+
+ConnectionManager::ConnectionManager(SystemState *system_state)
+    :  system_state_(system_state) {}
+
+bool ConnectionManager::IsUpdateAllowedOver(NetworkConnectionType type,
+                                            NetworkTethering tethering) const {
+  switch (type) {
+    case kNetBluetooth:
+      return false;
+
+    case kNetCellular: {
+      set<string> allowed_types;
+      const policy::DevicePolicy* device_policy =
+          system_state_->device_policy();
+
+      // A device_policy is loaded in a lazy way right before an update check,
+      // so the device_policy should be already loaded at this point. If it's
+      // not, return a safe value for this setting.
+      if (!device_policy) {
+        LOG(INFO) << "Disabling updates over cellular networks as there's no "
+                     "device policy loaded yet.";
+        return false;
+      }
+
+      if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+        // The update setting is enforced by the device policy.
+
+        if (!ContainsKey(allowed_types, shill::kTypeCellular)) {
+          LOG(INFO) << "Disabling updates over cellular connection as it's not "
+                       "allowed in the device policy.";
+          return false;
+        }
+
+        LOG(INFO) << "Allowing updates over cellular per device policy.";
+        return true;
+      } else {
+        // There's no update setting in the device policy, using the local user
+        // setting.
+        PrefsInterface* prefs = system_state_->prefs();
+
+        if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
+          LOG(INFO) << "Disabling updates over cellular connection as there's "
+                       "no device policy setting nor user preference present.";
+          return false;
+        }
+
+        bool stored_value;
+        if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission,
+                               &stored_value)) {
+          return false;
+        }
+
+        if (!stored_value) {
+          LOG(INFO) << "Disabling updates over cellular connection per user "
+                       "setting.";
+          return false;
+        }
+        LOG(INFO) << "Allowing updates over cellular per user setting.";
+        return true;
+      }
+    }
+
+    default:
+      if (tethering == NetworkTethering::kConfirmed) {
+        // Treat this connection as if it is a cellular connection.
+        LOG(INFO) << "Current connection is confirmed tethered, using Cellular "
+                     "setting.";
+        return IsUpdateAllowedOver(kNetCellular, NetworkTethering::kUnknown);
+      }
+      return true;
+  }
+}
+
+const char* ConnectionManager::StringForConnectionType(
+    NetworkConnectionType type) const {
+  static const char* const kValues[] = {shill::kTypeEthernet,
+                                        shill::kTypeWifi,
+                                        shill::kTypeWimax,
+                                        shill::kTypeBluetooth,
+                                        shill::kTypeCellular};
+  if (type < 0 || type >= static_cast<int>(arraysize(kValues))) {
+    return "Unknown";
+  }
+  return kValues[type];
+}
+
+const char* ConnectionManager::StringForTethering(
+    NetworkTethering tethering) const {
+  switch (tethering) {
+    case NetworkTethering::kNotDetected:
+      return shill::kTetheringNotDetectedState;
+    case NetworkTethering::kSuspected:
+      return shill::kTetheringSuspectedState;
+    case NetworkTethering::kConfirmed:
+      return shill::kTetheringConfirmedState;
+    case NetworkTethering::kUnknown:
+      return "Unknown";
+  }
+  // The program shouldn't reach this point, but the compiler isn't smart
+  // enough to infer that.
+  return "Unknown";
+}
+
+bool ConnectionManager::GetConnectionProperties(
+    DBusWrapperInterface* dbus_iface,
+    NetworkConnectionType* out_type,
+    NetworkTethering* out_tethering) const {
+  string default_service_path;
+  TEST_AND_RETURN_FALSE(GetDefaultServicePath(dbus_iface,
+                                              &default_service_path));
+  TEST_AND_RETURN_FALSE(GetServicePathProperties(dbus_iface,
+                                                 default_service_path,
+                                                 out_type, out_tethering));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/connection_manager.h b/connection_manager.h
new file mode 100644
index 0000000..f9973a3
--- /dev/null
+++ b/connection_manager.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_CONNECTION_MANAGER_H_
+
+#include <base/macros.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+
+namespace chromeos_update_engine {
+
+enum NetworkConnectionType {
+  kNetEthernet = 0,
+  kNetWifi,
+  kNetWimax,
+  kNetBluetooth,
+  kNetCellular,
+  kNetUnknown
+};
+
+enum class NetworkTethering {
+  kNotDetected = 0,
+  kSuspected,
+  kConfirmed,
+  kUnknown
+};
+
+class SystemState;
+
+// This class exposes a generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class ConnectionManager {
+ public:
+  // Constructs a new ConnectionManager object initialized with the
+  // given system state.
+  explicit ConnectionManager(SystemState* system_state);
+  virtual ~ConnectionManager() = default;
+
+  // Populates |out_type| with the type of the network connection
+  // that we are currently connected and |out_tethering| with the estimate of
+  // whether that network is being tethered. The dbus_iface is used to query
+  // the real connection manager (e.g shill).
+  virtual bool GetConnectionProperties(DBusWrapperInterface* dbus_iface,
+                                       NetworkConnectionType* out_type,
+                                       NetworkTethering* out_tethering) const;
+
+  // Returns true if we're allowed to update the system when we're
+  // connected to the internet through the given network connection type and the
+  // given tethering state.
+  virtual bool IsUpdateAllowedOver(NetworkConnectionType type,
+                                   NetworkTethering tethering) const;
+
+  // Returns the string representation corresponding to the given
+  // connection type.
+  virtual const char* StringForConnectionType(NetworkConnectionType type) const;
+
+  // Returns the string representation corresponding to the given tethering
+  // state.
+  virtual const char* StringForTethering(NetworkTethering tethering) const;
+
+ private:
+  // The global context for update_engine
+  SystemState* system_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(ConnectionManager);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CONNECTION_MANAGER_H_
diff --git a/connection_manager_unittest.cc b/connection_manager_unittest.cc
new file mode 100644
index 0000000..8ca1a00
--- /dev/null
+++ b/connection_manager_unittest.cc
@@ -0,0 +1,431 @@
+// Copyright (c) 2012 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/connection_manager.h"
+
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/test_utils.h"
+
+using std::set;
+using std::string;
+using testing::A;
+using testing::AnyNumber;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+class ConnectionManagerTest : public ::testing::Test {
+ public:
+  ConnectionManagerTest()
+      : kMockFlimFlamManagerProxy_(nullptr),
+        kMockFlimFlamServiceProxy_(nullptr),
+        kServicePath_(nullptr),
+        cmut_(&fake_system_state_) {
+    fake_system_state_.set_connection_manager(&cmut_);
+  }
+
+ protected:
+  void SetupMocks(const char* service_path);
+  void SetManagerReply(const char* reply_value, const GType& reply_type);
+
+  // Sets the |service_type| Type and the |physical_technology|
+  // PhysicalTechnology properties in the mocked service. If a null
+  // |physical_technology| is passed, the property is not set (not present).
+  void SetServiceReply(const char* service_type,
+                       const char* physical_technology,
+                       const char* service_tethering);
+  void TestWithServiceType(
+      const char* service_type,
+      const char* physical_technology,
+      NetworkConnectionType expected_type);
+  void TestWithServiceTethering(
+      const char* service_tethering,
+      NetworkTethering expected_tethering);
+
+  static const char* kGetPropertiesMethod;
+  DBusGProxy* kMockFlimFlamManagerProxy_;
+  DBusGProxy* kMockFlimFlamServiceProxy_;
+  DBusGConnection* kMockSystemBus_;
+  const char* kServicePath_;
+  testing::StrictMock<MockDBusWrapper> dbus_iface_;
+  ConnectionManager cmut_;  // ConnectionManager under test.
+  FakeSystemState fake_system_state_;
+};
+
+// static
+const char* ConnectionManagerTest::kGetPropertiesMethod = "GetProperties";
+
+void ConnectionManagerTest::SetupMocks(const char* service_path) {
+  int number = 1;
+  kMockSystemBus_ = reinterpret_cast<DBusGConnection*>(number++);
+  kMockFlimFlamManagerProxy_ = reinterpret_cast<DBusGProxy*>(number++);
+  kMockFlimFlamServiceProxy_ = reinterpret_cast<DBusGProxy*>(number++);
+  ASSERT_NE(kMockSystemBus_, static_cast<DBusGConnection*>(nullptr));
+
+  kServicePath_ = service_path;
+
+  ON_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+      .WillByDefault(Return(kMockSystemBus_));
+  EXPECT_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+      .Times(AnyNumber());
+}
+
+void ConnectionManagerTest::SetManagerReply(const char *reply_value,
+                                            const GType& reply_type) {
+  ASSERT_TRUE(dbus_g_type_is_collection(reply_type));
+
+  // Create the GPtrArray array holding the |reply_value| pointer. The
+  // |reply_value| string is duplicated because it should be mutable on the
+  // interface and is because dbus-glib collections will g_free() each element
+  // of the GPtrArray automatically when the |array_as_value| GValue is unset.
+  // The g_strdup() is not being leaked.
+  GPtrArray* array = g_ptr_array_new();
+  ASSERT_NE(nullptr, array);
+  g_ptr_array_add(array, g_strdup(reply_value));
+
+  GValue* array_as_value = g_new0(GValue, 1);
+  EXPECT_EQ(array_as_value, g_value_init(array_as_value, reply_type));
+  g_value_take_boxed(array_as_value, array);
+
+  // Initialize return value for D-Bus call to Manager object, which is a
+  // hash table of static strings (char*) in GValue* containing a single array.
+  GHashTable* manager_hash_table = g_hash_table_new_full(
+      g_str_hash, g_str_equal,
+      nullptr,  // no key_destroy_func because keys are static.
+      test_utils::GValueFree);  // value_destroy_func
+  g_hash_table_insert(manager_hash_table,
+                      const_cast<char*>("Services"),
+                      array_as_value);
+
+  // Plumb return value into mock object.
+  EXPECT_CALL(dbus_iface_, ProxyCall_0_1(kMockFlimFlamManagerProxy_,
+                                         StrEq(kGetPropertiesMethod),
+                                         _, A<GHashTable**>()))
+      .WillOnce(DoAll(SetArgumentPointee<3>(manager_hash_table), Return(TRUE)));
+
+  // Set other expectations.
+  EXPECT_CALL(dbus_iface_,
+              ProxyNewForName(kMockSystemBus_,
+                              StrEq(shill::kFlimflamServiceName),
+                              StrEq(shill::kFlimflamServicePath),
+                              StrEq(shill::kFlimflamManagerInterface)))
+      .WillOnce(Return(kMockFlimFlamManagerProxy_));
+  EXPECT_CALL(dbus_iface_, ProxyUnref(kMockFlimFlamManagerProxy_));
+  EXPECT_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+      .RetiresOnSaturation();
+}
+
+void ConnectionManagerTest::SetServiceReply(const char* service_type,
+                                            const char* physical_technology,
+                                            const char* service_tethering) {
+  // Initialize return value for D-Bus call to Service object, which is a
+  // hash table of static strings (char*) in GValue*.
+  GHashTable* service_hash_table = g_hash_table_new_full(
+      g_str_hash, g_str_equal,
+      nullptr,  // no key_destroy_func because keys are static.
+      test_utils::GValueFree);  // value_destroy_func
+  GValue* service_type_value = test_utils::GValueNewString(service_type);
+  g_hash_table_insert(service_hash_table,
+                      const_cast<char*>("Type"),
+                      service_type_value);
+
+  if (physical_technology) {
+    GValue* physical_technology_value =
+        test_utils::GValueNewString(physical_technology);
+    g_hash_table_insert(service_hash_table,
+                        const_cast<char*>("PhysicalTechnology"),
+                        physical_technology_value);
+  }
+
+  if (service_tethering) {
+    GValue* service_tethering_value =
+        test_utils::GValueNewString(service_tethering);
+    g_hash_table_insert(service_hash_table,
+                        const_cast<char*>("Tethering"),
+                        service_tethering_value);
+  }
+
+  // Plumb return value into mock object.
+  EXPECT_CALL(dbus_iface_, ProxyCall_0_1(kMockFlimFlamServiceProxy_,
+                                         StrEq(kGetPropertiesMethod),
+                                         _, A<GHashTable**>()))
+      .WillOnce(DoAll(SetArgumentPointee<3>(service_hash_table), Return(TRUE)));
+
+  // Set other expectations.
+  EXPECT_CALL(dbus_iface_,
+              ProxyNewForName(kMockSystemBus_,
+                              StrEq(shill::kFlimflamServiceName),
+                              StrEq(kServicePath_),
+                              StrEq(shill::kFlimflamServiceInterface)))
+      .WillOnce(Return(kMockFlimFlamServiceProxy_));
+  EXPECT_CALL(dbus_iface_, ProxyUnref(kMockFlimFlamServiceProxy_));
+  EXPECT_CALL(dbus_iface_, BusGet(DBUS_BUS_SYSTEM, _))
+      .RetiresOnSaturation();
+}
+
+void ConnectionManagerTest::TestWithServiceType(
+    const char* service_type,
+    const char* physical_technology,
+    NetworkConnectionType expected_type) {
+
+  SetupMocks("/service/guest-network");
+  SetManagerReply(kServicePath_, DBUS_TYPE_G_OBJECT_PATH_ARRAY);
+  SetServiceReply(service_type, physical_technology,
+                  shill::kTetheringNotDetectedState);
+
+  NetworkConnectionType type;
+  NetworkTethering tethering;
+  EXPECT_TRUE(cmut_.GetConnectionProperties(&dbus_iface_, &type, &tethering));
+  EXPECT_EQ(expected_type, type);
+  testing::Mock::VerifyAndClearExpectations(&dbus_iface_);
+}
+
+void ConnectionManagerTest::TestWithServiceTethering(
+    const char* service_tethering,
+    NetworkTethering expected_tethering) {
+
+  SetupMocks("/service/guest-network");
+  SetManagerReply(kServicePath_, DBUS_TYPE_G_OBJECT_PATH_ARRAY);
+  SetServiceReply(shill::kTypeWifi, nullptr, service_tethering);
+
+  NetworkConnectionType type;
+  NetworkTethering tethering;
+  EXPECT_TRUE(cmut_.GetConnectionProperties(&dbus_iface_, &type, &tethering));
+  EXPECT_EQ(expected_tethering, tethering);
+}
+
+TEST_F(ConnectionManagerTest, SimpleTest) {
+  TestWithServiceType(shill::kTypeEthernet, nullptr, kNetEthernet);
+  TestWithServiceType(shill::kTypeWifi, nullptr, kNetWifi);
+  TestWithServiceType(shill::kTypeWimax, nullptr, kNetWimax);
+  TestWithServiceType(shill::kTypeBluetooth, nullptr, kNetBluetooth);
+  TestWithServiceType(shill::kTypeCellular, nullptr, kNetCellular);
+}
+
+TEST_F(ConnectionManagerTest, PhysicalTechnologyTest) {
+  TestWithServiceType(shill::kTypeVPN, nullptr, kNetUnknown);
+  TestWithServiceType(shill::kTypeVPN, shill::kTypeVPN, kNetUnknown);
+  TestWithServiceType(shill::kTypeVPN, shill::kTypeWifi, kNetWifi);
+  TestWithServiceType(shill::kTypeVPN, shill::kTypeWimax, kNetWimax);
+}
+
+TEST_F(ConnectionManagerTest, TetheringTest) {
+  TestWithServiceTethering(shill::kTetheringConfirmedState,
+                           NetworkTethering::kConfirmed);
+  TestWithServiceTethering(shill::kTetheringNotDetectedState,
+                           NetworkTethering::kNotDetected);
+  TestWithServiceTethering(shill::kTetheringSuspectedState,
+                           NetworkTethering::kSuspected);
+  TestWithServiceTethering("I'm not a valid property value =)",
+                           NetworkTethering::kUnknown);
+}
+
+TEST_F(ConnectionManagerTest, UnknownTest) {
+  TestWithServiceType("foo", nullptr, kNetUnknown);
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverEthernetTest) {
+  // Updates over Ethernet are allowed even if there's no policy.
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+                                        NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverWifiTest) {
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi, NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverWimaxTest) {
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWimax,
+                                        NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverBluetoothTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetBluetooth,
+                                         NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOnlyOver3GPerPolicyTest) {
+  policy::MockDevicePolicy allow_3g_policy;
+
+  fake_system_state_.set_device_policy(&allow_3g_policy);
+
+  // This test tests cellular (3G) being the only connection type being allowed.
+  set<string> allowed_set;
+  allowed_set.insert(cmut_.StringForConnectionType(kNetCellular));
+
+  EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(1)
+      .WillOnce(DoAll(SetArgumentPointee<0>(allowed_set), Return(true)));
+
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                        NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOver3GAndOtherTypesPerPolicyTest) {
+  policy::MockDevicePolicy allow_3g_policy;
+
+  fake_system_state_.set_device_policy(&allow_3g_policy);
+
+  // This test tests multiple connection types being allowed, with
+  // 3G one among them. Only Cellular is currently enforced by the policy
+  // setting, the others are ignored (see Bluetooth for example).
+  set<string> allowed_set;
+  allowed_set.insert(cmut_.StringForConnectionType(kNetCellular));
+  allowed_set.insert(cmut_.StringForConnectionType(kNetBluetooth));
+
+  EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(3)
+      .WillRepeatedly(DoAll(SetArgumentPointee<0>(allowed_set), Return(true)));
+
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+                                        NetworkTethering::kUnknown));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+                                        NetworkTethering::kNotDetected));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                        NetworkTethering::kUnknown));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi, NetworkTethering::kUnknown));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWimax, NetworkTethering::kUnknown));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetBluetooth,
+                                         NetworkTethering::kUnknown));
+
+  // Tethered networks are treated in the same way as Cellular networks and
+  // thus allowed.
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+                                        NetworkTethering::kConfirmed));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi,
+                                        NetworkTethering::kConfirmed));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverCellularByDefaultTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                         NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverTetheredNetworkByDefaultTest) {
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetWifi,
+                                         NetworkTethering::kConfirmed));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetEthernet,
+                                         NetworkTethering::kConfirmed));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetWifi,
+                                        NetworkTethering::kSuspected));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GPerPolicyTest) {
+  policy::MockDevicePolicy block_3g_policy;
+
+  fake_system_state_.set_device_policy(&block_3g_policy);
+
+  // Test that updates for 3G are blocked while updates are allowed
+  // over several other types.
+  set<string> allowed_set;
+  allowed_set.insert(cmut_.StringForConnectionType(kNetEthernet));
+  allowed_set.insert(cmut_.StringForConnectionType(kNetWifi));
+  allowed_set.insert(cmut_.StringForConnectionType(kNetWimax));
+
+  EXPECT_CALL(block_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(1)
+      .WillOnce(DoAll(SetArgumentPointee<0>(allowed_set), Return(true)));
+
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                         NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GIfErrorInPolicyFetchTest) {
+  policy::MockDevicePolicy allow_3g_policy;
+
+  fake_system_state_.set_device_policy(&allow_3g_policy);
+
+  set<string> allowed_set;
+  allowed_set.insert(cmut_.StringForConnectionType(kNetCellular));
+
+  // Return false for GetAllowedConnectionTypesForUpdate and see
+  // that updates are still blocked for 3G despite the value being in
+  // the string set above.
+  EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(1)
+      .WillOnce(DoAll(SetArgumentPointee<0>(allowed_set), Return(false)));
+
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                         NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, UseUserPrefForUpdatesOverCellularIfNoPolicyTest) {
+  policy::MockDevicePolicy no_policy;
+  testing::NiceMock<MockPrefs>* prefs = fake_system_state_.mock_prefs();
+
+  fake_system_state_.set_device_policy(&no_policy);
+
+  // No setting enforced by the device policy, user prefs should be used.
+  EXPECT_CALL(no_policy, GetAllowedConnectionTypesForUpdate(_))
+      .Times(3)
+      .WillRepeatedly(Return(false));
+
+  // No user pref: block.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(false));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                         NetworkTethering::kUnknown));
+
+  // Allow per user pref.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+      .Times(1)
+      .WillOnce(DoAll(SetArgumentPointee<1>(true), Return(true)));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                        NetworkTethering::kUnknown));
+
+  // Block per user pref.
+  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+      .Times(1)
+      .WillOnce(Return(true));
+  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+      .Times(1)
+      .WillOnce(DoAll(SetArgumentPointee<1>(false), Return(true)));
+  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(kNetCellular,
+                                         NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) {
+  EXPECT_STREQ(shill::kTypeEthernet,
+               cmut_.StringForConnectionType(kNetEthernet));
+  EXPECT_STREQ(shill::kTypeWifi,
+               cmut_.StringForConnectionType(kNetWifi));
+  EXPECT_STREQ(shill::kTypeWimax,
+               cmut_.StringForConnectionType(kNetWimax));
+  EXPECT_STREQ(shill::kTypeBluetooth,
+               cmut_.StringForConnectionType(kNetBluetooth));
+  EXPECT_STREQ(shill::kTypeCellular,
+               cmut_.StringForConnectionType(kNetCellular));
+  EXPECT_STREQ("Unknown",
+               cmut_.StringForConnectionType(kNetUnknown));
+  EXPECT_STREQ("Unknown",
+               cmut_.StringForConnectionType(
+                   static_cast<NetworkConnectionType>(999999)));
+}
+
+TEST_F(ConnectionManagerTest, MalformedServiceList) {
+  SetupMocks("/service/guest-network");
+  SetManagerReply(kServicePath_, DBUS_TYPE_G_STRING_ARRAY);
+
+  NetworkConnectionType type;
+  NetworkTethering tethering;
+  EXPECT_FALSE(cmut_.GetConnectionProperties(&dbus_iface_, &type, &tethering));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/constants.cc b/constants.cc
new file mode 100644
index 0000000..0e052c0
--- /dev/null
+++ b/constants.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2013 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/constants.h"
+
+namespace chromeos_update_engine {
+
+const char kPowerwashMarkerFile[] =
+    "/mnt/stateful_partition/factory_install_reset";
+
+const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n";
+
+const char kPowerwashSafePrefsDir[] =
+    "/mnt/stateful_partition/unencrypted/preserve/update_engine/prefs";
+
+const char kPrefsDirectory[] = "/var/lib/update_engine/prefs";
+
+const char kStatefulPartition[] = "/mnt/stateful_partition";
+
+const char kSystemRebootedMarkerFile[] = "/tmp/update_engine_update_recorded";
+
+// Constants defining keys for the persisted state of update engine.
+const char kPrefsAttemptInProgress[] = "attempt-in-progress";
+const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time";
+const char kPrefsCertificateReportToSendDownload[] =
+    "certificate-report-to-send-download";
+const char kPrefsCertificateReportToSendUpdate[] =
+    "certificate-report-to-send-update";
+const char kPrefsCurrentBytesDownloaded[] = "current-bytes-downloaded";
+const char kPrefsCurrentResponseSignature[] = "current-response-signature";
+const char kPrefsCurrentUrlFailureCount[] = "current-url-failure-count";
+const char kPrefsCurrentUrlIndex[] = "current-url-index";
+const char kPrefsDailyMetricsLastReportedAt[] =
+    "daily-metrics-last-reported-at";
+const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
+const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number";
+const char kPrefsInstallDateDays[] = "install-date-days";
+const char kPrefsLastActivePingDay[] = "last-active-ping-day";
+const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
+const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
+const char kPrefsMetricsAttemptLastReportingTime[] =
+    "metrics-attempt-last-reporting-time";
+const char kPrefsMetricsCheckLastReportingTime[] =
+    "metrics-check-last-reporting-time";
+const char kPrefsNumReboots[] = "num-reboots";
+const char kPrefsNumResponsesSeen[] = "num-responses-seen";
+const char kPrefsOmahaCohort[] = "omaha-cohort";
+const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
+const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
+const char kPrefsP2PEnabled[] = "p2p-enabled";
+const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
+const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
+const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
+const char kPrefsPreviousVersion[] = "previous-version";
+const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
+const char kPrefsRollbackVersion[] = "rollback-version";
+const char kPrefsSystemUpdatedMarker[] = "system-updated-marker";
+const char kPrefsTargetVersionAttempt[] = "target-version-attempt";
+const char kPrefsTargetVersionInstalledFrom[] = "target-version-installed-from";
+const char kPrefsTargetVersionUniqueId[] = "target-version-unique-id";
+const char kPrefsTotalBytesDownloaded[] = "total-bytes-downloaded";
+const char kPrefsUpdateCheckCount[] = "update-check-count";
+const char kPrefsUpdateCheckResponseHash[] = "update-check-response-hash";
+const char kPrefsUpdateDurationUptime[] = "update-duration-uptime";
+const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
+const char kPrefsUpdateOverCellularPermission[] =
+    "update-over-cellular-permission";
+const char kPrefsUpdateServerCertificate[] = "update-server-cert";
+const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
+const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
+const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
+const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
+const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
+const char kPrefsUpdateStateSignedSHA256Context[] =
+    "update-state-signed-sha-256-context";
+const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
+const char kPrefsUrlSwitchCount[] = "url-switch-count";
+const char kPrefsWallClockWaitPeriod[] = "wall-clock-wait-period";
+
+}  // namespace chromeos_update_engine
diff --git a/constants.h b/constants.h
new file mode 100644
index 0000000..b933f62
--- /dev/null
+++ b/constants.h
@@ -0,0 +1,169 @@
+// Copyright (c) 2013 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_CONSTANTS_H_
+#define UPDATE_ENGINE_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+
+// The name of the marker file used to trigger powerwash when post-install
+// completes successfully so that the device is powerwashed on next reboot.
+extern const char kPowerwashMarkerFile[];
+
+// Path to the marker file we use to indicate we've recorded a system reboot.
+extern const char kSystemRebootedMarkerFile[];
+
+// The contents of the powerwash marker file.
+extern const char kPowerwashCommand[];
+
+// Directory for AU prefs that are preserved across powerwash.
+extern const char kPowerwashSafePrefsDir[];
+
+// The location where we store the AU preferences (state etc).
+extern const char kPrefsDirectory[];
+
+// Path to the stateful partition on the root filesystem.
+extern const char kStatefulPartition[];
+
+// Constants related to preferences.
+extern const char kPrefsAttemptInProgress[];
+extern const char kPrefsBackoffExpiryTime[];
+extern const char kPrefsCertificateReportToSendDownload[];
+extern const char kPrefsCertificateReportToSendUpdate[];
+extern const char kPrefsCurrentBytesDownloaded[];
+extern const char kPrefsCurrentResponseSignature[];
+extern const char kPrefsCurrentUrlFailureCount[];
+extern const char kPrefsCurrentUrlIndex[];
+extern const char kPrefsDailyMetricsLastReportedAt[];
+extern const char kPrefsDeltaUpdateFailures[];
+extern const char kPrefsFullPayloadAttemptNumber[];
+extern const char kPrefsInstallDateDays[];
+extern const char kPrefsLastActivePingDay[];
+extern const char kPrefsLastRollCallPingDay[];
+extern const char kPrefsManifestMetadataSize[];
+extern const char kPrefsMetricsAttemptLastReportingTime[];
+extern const char kPrefsMetricsCheckLastReportingTime[];
+extern const char kPrefsNumReboots[];
+extern const char kPrefsNumResponsesSeen[];
+extern const char kPrefsOmahaCohort[];
+extern const char kPrefsOmahaCohortHint[];
+extern const char kPrefsOmahaCohortName[];
+extern const char kPrefsP2PEnabled[];
+extern const char kPrefsP2PFirstAttemptTimestamp[];
+extern const char kPrefsP2PNumAttempts[];
+extern const char kPrefsPayloadAttemptNumber[];
+extern const char kPrefsPreviousVersion[];
+extern const char kPrefsResumedUpdateFailures[];
+extern const char kPrefsRollbackVersion[];
+extern const char kPrefsSystemUpdatedMarker[];
+extern const char kPrefsTargetVersionAttempt[];
+extern const char kPrefsTargetVersionInstalledFrom[];
+extern const char kPrefsTargetVersionUniqueId[];
+extern const char kPrefsTotalBytesDownloaded[];
+extern const char kPrefsUpdateCheckCount[];
+extern const char kPrefsUpdateCheckResponseHash[];
+extern const char kPrefsUpdateDurationUptime[];
+extern const char kPrefsUpdateFirstSeenAt[];
+extern const char kPrefsUpdateOverCellularPermission[];
+extern const char kPrefsUpdateServerCertificate[];
+extern const char kPrefsUpdateStateNextDataLength[];
+extern const char kPrefsUpdateStateNextDataOffset[];
+extern const char kPrefsUpdateStateNextOperation[];
+extern const char kPrefsUpdateStateSHA256Context[];
+extern const char kPrefsUpdateStateSignatureBlob[];
+extern const char kPrefsUpdateStateSignedSHA256Context[];
+extern const char kPrefsUpdateTimestampStart[];
+extern const char kPrefsUrlSwitchCount[];
+extern const char kPrefsWallClockWaitPeriod[];
+
+// A download source is any combination of protocol and server (that's of
+// interest to us when looking at UMA metrics) using which we may download
+// the payload.
+typedef enum {
+  kDownloadSourceHttpsServer,  // UMA Binary representation: 0001
+  kDownloadSourceHttpServer,   // UMA Binary representation: 0010
+  kDownloadSourceHttpPeer,     // UMA Binary representation: 0100
+
+  // Note: Add new sources only above this line.
+  kNumDownloadSources
+} DownloadSource;
+
+// A payload can be a Full or Delta payload. In some cases, a Full payload is
+// used even when a Delta payload was available for the update, called here
+// ForcedFull. The PayloadType enum is only used to send UMA metrics about the
+// successfully applied payload.
+typedef enum {
+  kPayloadTypeFull,
+  kPayloadTypeDelta,
+  kPayloadTypeForcedFull,
+
+  // Note: Add new payload types only above this line.
+  kNumPayloadTypes
+} PayloadType;
+
+// Maximum number of times we'll allow using p2p for the same update payload.
+const int kMaxP2PAttempts = 10;
+
+// Maximum wallclock time we allow attempting to update using p2p for
+// the same update payload - five days.
+const int kMaxP2PAttemptTimeSeconds = 5 * 24 * 60 * 60;
+
+// The maximum amount of time to spend waiting for p2p-client(1) to
+// return while waiting in line to use the LAN - six hours.
+const int kMaxP2PNetworkWaitTimeSeconds = 6 * 60 * 60;
+
+// The maximum number of payload files to keep in /var/cache/p2p.
+const int kMaxP2PFilesToKeep = 3;
+
+// The maximum number of days to keep a p2p file;
+const int kMaxP2PFileAgeDays = 5;
+
+// The default number of UMA buckets for metrics.
+const int kNumDefaultUmaBuckets = 50;
+
+// General constants
+const int kNumBytesInOneMiB = 1024 * 1024;
+
+// Number of redirects allowed when downloading.
+const int kDownloadMaxRedirects = 10;
+
+// The minimum average speed that downloads must sustain...
+//
+// This is set low because some devices may have very poor
+// connectivity and we want to make as much forward progress as
+// possible. For p2p this is high (25 kB/second) since we can assume
+// high bandwidth (same LAN) and we want to fail fast.
+const int kDownloadLowSpeedLimitBps = 1;
+const int kDownloadP2PLowSpeedLimitBps = 25 * 1000;
+
+// ... measured over this period.
+//
+// For non-official builds (e.g. typically built on a developer's
+// workstation and served via devserver) bump this since it takes time
+// for the workstation to generate the payload. For p2p, make this
+// relatively low since we want to fail fast.
+const int kDownloadLowSpeedTimeSeconds = 90;
+const int kDownloadDevModeLowSpeedTimeSeconds = 180;
+const int kDownloadP2PLowSpeedTimeSeconds = 60;
+
+// The maximum amount of HTTP server reconnect attempts.
+//
+// This is set high in order to maximize the attempt's chance of
+// succeeding. When using p2p, this is low in order to fail fast.
+const int kDownloadMaxRetryCount = 20;
+const int kDownloadMaxRetryCountOobeNotComplete = 3;
+const int kDownloadP2PMaxRetryCount = 5;
+
+// The connect timeout, in seconds.
+//
+// This is set high because some devices may have very poor
+// connectivity and we may be using HTTPS which involves complicated
+// multi-roundtrip setup. For p2p, this is set low because we can
+// the server is on the same LAN and we want to fail fast.
+const int kDownloadConnectTimeoutSeconds = 30;
+const int kDownloadP2PConnectTimeoutSeconds = 5;
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_CONSTANTS_H_
diff --git a/dbus_bindings/dbus-service-config.json b/dbus_bindings/dbus-service-config.json
new file mode 100644
index 0000000..fdae3ba
--- /dev/null
+++ b/dbus_bindings/dbus-service-config.json
@@ -0,0 +1,3 @@
+{
+  "service_name": "org.chromium.UpdateEngine"
+}
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.xml b/dbus_bindings/org.chromium.UpdateEngineInterface.xml
new file mode 100644
index 0000000..dda972d
--- /dev/null
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- COPYRIGHT HERE
+     dbus-binding-tool -mode=glib-server -prefix=update_engine update_engine.xml
+                        &gt; glib_server.h
+-->
+<node name="/org/chromium/UpdateEngine">
+  <interface name="org.chromium.UpdateEngineInterface">
+    <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+                value="update_engine_service" />
+    <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+                value="update_engine_client" />
+    <method name="AttemptUpdate">
+      <arg type="s" name="app_version" direction="in" />
+      <arg type="s" name="omaha_url" direction="in" />
+    </method>
+    <!-- TODO(zeuthen,chromium:286399): Rename to AttemptUpdate and
+         update Chrome and other users of the AttemptUpdate() method
+         in lockstep.
+    -->
+    <method name="AttemptUpdateWithFlags">
+      <arg type="s" name="app_version" direction="in" />
+      <arg type="s" name="omaha_url" direction="in" />
+      <!-- See AttemptUpdateFlags enum in dbus_constants.h. -->
+      <arg type="i" name="flags" direction="in" />
+    </method>
+    <method name="AttemptRollback">
+      <arg type="b" name="powerwash" direction="in" />
+    </method>
+    <method name="CanRollback">
+      <arg type="b" name="can_rollback" direction="out" />
+    </method>
+    <method name="ResetStatus">
+    </method>
+    <method name="GetStatus">
+      <arg type="x" name="last_checked_time" direction="out" />
+      <arg type="d" name="progress" direction="out" />
+      <arg type="s" name="current_operation" direction="out" />
+      <arg type="s" name="new_version" direction="out" />
+      <arg type="x" name="new_size" direction="out" />
+    </method>
+    <method name="RebootIfNeeded">
+    </method>
+    <method name="SetChannel">
+      <arg type="s" name="target_channel" direction="in" />
+      <arg type="b" name="is_powerwash_allowed" direction="in" />
+    </method>
+    <method name="GetChannel">
+      <arg type="b" name="get_current_channel" direction="in" />
+      <arg type="s" name="channel" direction="out" />
+    </method>
+    <method name="SetP2PUpdatePermission">
+      <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+        value="update_engine_service_set_p2p_update_permission" />
+      <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+        value="update_engine_client_set_p2p_update_permission" />
+      <arg type="b" name="enabled" direction="in" />
+    </method>
+    <method name="GetP2PUpdatePermission">
+      <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+        value="update_engine_service_get_p2p_update_permission" />
+      <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+        value="update_engine_client_get_p2p_update_permission" />
+      <arg type="b" name="enabled" direction="out" />
+    </method>
+    <method name="SetUpdateOverCellularPermission">
+      <arg type="b" name="allowed" direction="in" />
+    </method>
+    <method name="GetUpdateOverCellularPermission">
+      <arg type="b" name="allowed" direction="out" />
+    </method>
+    <method name="GetDurationSinceUpdate">
+      <arg type="x" name="usec_wallclock" direction="out" />
+    </method>
+    <signal name="StatusUpdate">
+      <arg type="x" name="last_checked_time" />
+      <arg type="d" name="progress" />
+      <arg type="s" name="current_operation" />
+      <arg type="s" name="new_version" />
+      <arg type="x" name="new_size" />
+    </signal>
+    <method name="GetPrevVersion">
+      <arg type="s" name="prev_version" direction="out" />
+    </method>
+    <method name="GetKernelDevices">
+      <arg type="s" name="kernel_devices" direction="out" />
+    </method>
+    <method name="GetRollbackPartition">
+      <arg type="s" name="rollback_partition_name" direction="out" />
+    </method>
+  </interface>
+</node>
diff --git a/dbus_constants.h b/dbus_constants.h
new file mode 100644
index 0000000..d51e6fd
--- /dev/null
+++ b/dbus_constants.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2010 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_DBUS_CONSTANTS_H_
+#define UPDATE_ENGINE_DBUS_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+
+static const char* const kUpdateEngineServiceName = "org.chromium.UpdateEngine";
+static const char* const kUpdateEngineServicePath =
+    "/org/chromium/UpdateEngine";
+static const char* const kUpdateEngineServiceInterface =
+    "org.chromium.UpdateEngineInterface";
+
+// Flags used in the AttemptUpdateWithFlags() D-Bus method.
+typedef enum {
+  kAttemptUpdateFlagNonInteractive = (1<<0)
+} AttemptUpdateFlags;
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DBUS_CONSTANTS_H_
diff --git a/dbus_service.cc b/dbus_service.cc
new file mode 100644
index 0000000..a7ec259
--- /dev/null
+++ b/dbus_service.cc
@@ -0,0 +1,476 @@
+// Copyright (c) 2012 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/dbus_service.h"
+
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/strings/string_utils.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/dbus_constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/prefs.h"
+#include "update_engine/update_attempter.h"
+#include "update_engine/utils.h"
+
+using base::StringPrintf;
+using chromeos::string_utils::ToString;
+using chromeos_update_engine::AttemptUpdateFlags;
+using chromeos_update_engine::kAttemptUpdateFlagNonInteractive;
+using std::set;
+using std::string;
+
+#define UPDATE_ENGINE_SERVICE_ERROR update_engine_service_error_quark ()
+#define UPDATE_ENGINE_SERVICE_TYPE_ERROR \
+  (update_engine_service_error_get_type())
+
+enum UpdateEngineServiceError {
+  UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+  UPDATE_ENGINE_SERVICE_NUM_ERRORS
+};
+
+static GQuark update_engine_service_error_quark(void) {
+  static GQuark ret = 0;
+
+  if (ret == 0)
+    ret = g_quark_from_static_string("update_engine_service_error");
+
+  return ret;
+}
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+
+static GType update_engine_service_error_get_type(void) {
+  static GType etype = 0;
+
+  if (etype == 0) {
+    static const GEnumValue values[] = {
+      ENUM_ENTRY(UPDATE_ENGINE_SERVICE_ERROR_FAILED, "Failed"),
+      { 0, 0, 0 }
+    };
+    G_STATIC_ASSERT(UPDATE_ENGINE_SERVICE_NUM_ERRORS ==
+                    G_N_ELEMENTS(values) - 1);
+    etype = g_enum_register_static("UpdateEngineServiceError", values);
+  }
+
+  return etype;
+}
+
+G_DEFINE_TYPE(UpdateEngineService, update_engine_service, G_TYPE_OBJECT)
+
+static void update_engine_service_finalize(GObject* object) {
+  G_OBJECT_CLASS(update_engine_service_parent_class)->finalize(object);
+}
+
+static void log_and_set_response_error(GError** error,
+                                       UpdateEngineServiceError error_code,
+                                       const string& reason) {
+  LOG(ERROR) << "Sending DBus Failure: " << reason;
+  g_set_error_literal(error, UPDATE_ENGINE_SERVICE_ERROR,
+                      error_code, reason.c_str());
+}
+
+static guint status_update_signal = 0;
+
+static void update_engine_service_class_init(UpdateEngineServiceClass* klass) {
+  GObjectClass *object_class;
+  object_class = G_OBJECT_CLASS(klass);
+  object_class->finalize = update_engine_service_finalize;
+
+  status_update_signal = g_signal_new(
+      "status_update",
+      G_OBJECT_CLASS_TYPE(klass),
+      G_SIGNAL_RUN_LAST,
+      0,  // 0 == no class method associated
+      nullptr,  // Accumulator
+      nullptr,  // Accumulator data
+      nullptr,  // Marshaller
+      G_TYPE_NONE,  // Return type
+      5,  // param count:
+      G_TYPE_INT64,
+      G_TYPE_DOUBLE,
+      G_TYPE_STRING,
+      G_TYPE_STRING,
+      G_TYPE_INT64);
+}
+
+static void update_engine_service_init(UpdateEngineService* object) {
+  dbus_g_error_domain_register(UPDATE_ENGINE_SERVICE_ERROR,
+                               "org.chromium.UpdateEngine.Error",
+                               UPDATE_ENGINE_SERVICE_TYPE_ERROR);
+}
+
+UpdateEngineService* update_engine_service_new(void) {
+  return reinterpret_cast<UpdateEngineService*>(
+      g_object_new(UPDATE_ENGINE_TYPE_SERVICE, nullptr));
+}
+
+gboolean update_engine_service_attempt_update(UpdateEngineService* self,
+                                              gchar* app_version,
+                                              gchar* omaha_url,
+                                              GError **error) {
+  return update_engine_service_attempt_update_with_flags(self,
+                                                         app_version,
+                                                         omaha_url,
+                                                         0,  // No flags set.
+                                                         error);
+}
+
+gboolean update_engine_service_attempt_update_with_flags(
+    UpdateEngineService* self,
+    gchar* app_version,
+    gchar* omaha_url,
+    gint flags_as_int,
+    GError **error) {
+  string app_version_string, omaha_url_string;
+  AttemptUpdateFlags flags = static_cast<AttemptUpdateFlags>(flags_as_int);
+  bool interactive = !(flags & kAttemptUpdateFlagNonInteractive);
+
+  if (app_version)
+    app_version_string = app_version;
+  if (omaha_url)
+    omaha_url_string = omaha_url;
+
+  LOG(INFO) << "Attempt update: app_version=\"" << app_version_string << "\" "
+            << "omaha_url=\"" << omaha_url_string << "\" "
+            << "flags=0x" << std::hex << flags << " "
+            << "interactive=" << (interactive? "yes" : "no");
+  self->system_state_->update_attempter()->CheckForUpdate(app_version_string,
+                                                          omaha_url_string,
+                                                          interactive);
+  return TRUE;
+}
+
+gboolean update_engine_service_attempt_rollback(UpdateEngineService* self,
+                                                gboolean powerwash,
+                                                GError **error) {
+  LOG(INFO) << "Attempting rollback to non-active partitions.";
+
+  if (!self->system_state_->update_attempter()->Rollback(powerwash)) {
+    // TODO(dgarrett): Give a more specific error code/reason.
+    log_and_set_response_error(error,
+                               UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "Rollback attempt failed.");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+gboolean update_engine_service_can_rollback(UpdateEngineService* self,
+                                            gboolean* out_can_rollback,
+                                            GError **error) {
+  bool can_rollback = self->system_state_->update_attempter()->CanRollback();
+  LOG(INFO) << "Checking to see if we can rollback . Result: " << can_rollback;
+  *out_can_rollback = can_rollback;
+  return TRUE;
+}
+
+gboolean update_engine_service_get_rollback_partition(
+    UpdateEngineService* self,
+    gchar** out_rollback_partition_name,
+    GError **error) {
+  auto name = self->system_state_->update_attempter()->GetRollbackPartition();
+  LOG(INFO) << "Getting rollback partition name. Result: " << name;
+  *out_rollback_partition_name = g_strdup(name.c_str());
+  return TRUE;
+}
+
+gboolean update_engine_service_get_kernel_devices(UpdateEngineService* self,
+                                                  gchar** out_kernel_devices,
+                                                  GError **error) {
+  auto devices = self->system_state_->update_attempter()->GetKernelDevices();
+  string info;
+  for (const auto& device : devices) {
+    base::StringAppendF(&info, "%d:%s\n",
+                        device.second ? 1 : 0, device.first.c_str());
+  }
+  LOG(INFO) << "Available kernel devices: " << info;
+  *out_kernel_devices = g_strdup(info.c_str());
+  return TRUE;
+}
+
+
+gboolean update_engine_service_reset_status(UpdateEngineService* self,
+                                            GError **error) {
+  if (!self->system_state_->update_attempter()->ResetStatus()) {
+    // TODO(dgarrett): Give a more specific error code/reason.
+    log_and_set_response_error(error,
+                               UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "ResetStatus failed.");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+gboolean update_engine_service_get_status(UpdateEngineService* self,
+                                          int64_t* last_checked_time,
+                                          double* progress,
+                                          gchar** current_operation,
+                                          gchar** new_version,
+                                          int64_t* new_size,
+                                          GError **error) {
+  string current_op;
+  string new_version_str;
+
+  CHECK(self->system_state_->update_attempter()->GetStatus(last_checked_time,
+                                                           progress,
+                                                           &current_op,
+                                                           &new_version_str,
+                                                           new_size));
+  *current_operation = g_strdup(current_op.c_str());
+  *new_version = g_strdup(new_version_str.c_str());
+
+  if (!*current_operation) {
+    log_and_set_response_error(error,
+                               UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "Unable to find current_operation.");
+    return FALSE;
+  }
+
+  if (!*new_version) {
+    log_and_set_response_error(error,
+                               UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "Unable to find vew_version.");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+gboolean update_engine_service_reboot_if_needed(UpdateEngineService* self,
+                                                GError **error) {
+  if (!self->system_state_->update_attempter()->RebootIfNeeded()) {
+    // TODO(dgarrett): Give a more specific error code/reason.
+    log_and_set_response_error(error,
+                               UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "Reboot not needed, or attempt failed.");
+    return FALSE;
+  }
+  return TRUE;
+}
+
+gboolean update_engine_service_set_channel(UpdateEngineService* self,
+                                           gchar* target_channel,
+                                           gboolean is_powerwash_allowed,
+                                           GError **error) {
+  if (!target_channel) {
+    log_and_set_response_error(error,
+                               UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "Target channel to set not specified.");
+    return FALSE;
+  }
+
+  const policy::DevicePolicy* device_policy =
+      self->system_state_->device_policy();
+
+  // The device_policy is loaded in a lazy way before an update check. Load it
+  // now from the libchromeos cache if it wasn't already loaded.
+  if (!device_policy) {
+    chromeos_update_engine::UpdateAttempter* update_attempter =
+        self->system_state_->update_attempter();
+    if (update_attempter) {
+      update_attempter->RefreshDevicePolicy();
+      device_policy = self->system_state_->device_policy();
+    }
+  }
+
+  bool delegated = false;
+  if (device_policy &&
+      device_policy->GetReleaseChannelDelegated(&delegated) && !delegated) {
+    log_and_set_response_error(
+        error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+        "Cannot set target channel explicitly when channel "
+        "policy/settings is not delegated");
+    return FALSE;
+  }
+
+  LOG(INFO) << "Setting destination channel to: " << target_channel;
+  if (!self->system_state_->request_params()->SetTargetChannel(
+          target_channel, is_powerwash_allowed)) {
+    // TODO(dgarrett): Give a more specific error code/reason.
+    log_and_set_response_error(error,
+                               UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "Setting channel failed.");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+gboolean update_engine_service_get_channel(UpdateEngineService* self,
+                                           gboolean get_current_channel,
+                                           gchar** channel,
+                                           GError **error) {
+  chromeos_update_engine::OmahaRequestParams* rp =
+      self->system_state_->request_params();
+
+  string channel_str = get_current_channel ?
+      rp->current_channel() : rp->target_channel();
+
+  *channel = g_strdup(channel_str.c_str());
+  return TRUE;
+}
+
+gboolean update_engine_service_set_p2p_update_permission(
+    UpdateEngineService* self,
+    gboolean enabled,
+    GError **error) {
+  chromeos_update_engine::PrefsInterface* prefs = self->system_state_->prefs();
+
+  if (!prefs->SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, enabled)) {
+    log_and_set_response_error(
+        error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+        StringPrintf("Error setting the update via p2p permission to %s.",
+                     ToString(enabled).c_str()));
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+gboolean update_engine_service_get_p2p_update_permission(
+    UpdateEngineService* self,
+    gboolean* enabled,
+    GError **error) {
+  chromeos_update_engine::PrefsInterface* prefs = self->system_state_->prefs();
+
+  bool p2p_pref = false;  // Default if no setting is present.
+  if (prefs->Exists(chromeos_update_engine::kPrefsP2PEnabled) &&
+      !prefs->GetBoolean(chromeos_update_engine::kPrefsP2PEnabled, &p2p_pref)) {
+    log_and_set_response_error(error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "Error getting the P2PEnabled setting.");
+    return FALSE;
+  }
+
+  *enabled = p2p_pref;
+  return TRUE;
+}
+
+gboolean update_engine_service_set_update_over_cellular_permission(
+    UpdateEngineService* self,
+    gboolean allowed,
+    GError **error) {
+  set<string> allowed_types;
+  const policy::DevicePolicy* device_policy =
+      self->system_state_->device_policy();
+
+  // The device_policy is loaded in a lazy way before an update check. Load it
+  // now from the libchromeos cache if it wasn't already loaded.
+  if (!device_policy) {
+    chromeos_update_engine::UpdateAttempter* update_attempter =
+        self->system_state_->update_attempter();
+    if (update_attempter) {
+      update_attempter->RefreshDevicePolicy();
+      device_policy = self->system_state_->device_policy();
+    }
+  }
+
+  // Check if this setting is allowed by the device policy.
+  if (device_policy &&
+      device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+    log_and_set_response_error(
+        error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+        "Ignoring the update over cellular setting since there's "
+        "a device policy enforcing this setting.");
+    return FALSE;
+  }
+
+  // If the policy wasn't loaded yet, then it is still OK to change the local
+  // setting because the policy will be checked again during the update check.
+
+  chromeos_update_engine::PrefsInterface* prefs = self->system_state_->prefs();
+
+  if (!prefs->SetBoolean(
+      chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+      allowed)) {
+    log_and_set_response_error(
+        error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+        string("Error setting the update over cellular to ") +
+        (allowed ? "true" : "false"));
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+gboolean update_engine_service_get_update_over_cellular_permission(
+    UpdateEngineService* self,
+    gboolean* allowed,
+    GError **error) {
+  chromeos_update_engine::ConnectionManager* cm =
+      self->system_state_->connection_manager();
+
+  // The device_policy is loaded in a lazy way before an update check and is
+  // used to determine if an update is allowed over cellular. Load the device
+  // policy now from the libchromeos cache if it wasn't already loaded.
+  if (!self->system_state_->device_policy()) {
+    chromeos_update_engine::UpdateAttempter* update_attempter =
+        self->system_state_->update_attempter();
+    if (update_attempter)
+      update_attempter->RefreshDevicePolicy();
+  }
+
+  // Return the current setting based on the same logic used while checking for
+  // updates. A log message could be printed as the result of this test.
+  LOG(INFO) << "Checking if updates over cellular networks are allowed:";
+  *allowed = cm->IsUpdateAllowedOver(
+      chromeos_update_engine::kNetCellular,
+      chromeos_update_engine::NetworkTethering::kUnknown);
+
+  return TRUE;
+}
+
+gboolean update_engine_service_get_duration_since_update(
+    UpdateEngineService* self,
+    gint64* out_usec_wallclock,
+    GError **error) {
+
+  base::Time time;
+  if (!self->system_state_->update_attempter()->GetBootTimeAtUpdate(&time)) {
+    log_and_set_response_error(error, UPDATE_ENGINE_SERVICE_ERROR_FAILED,
+                               "No pending update.");
+    return FALSE;
+  }
+
+  chromeos_update_engine::ClockInterface *clock = self->system_state_->clock();
+  *out_usec_wallclock = (clock->GetBootTime() - time).InMicroseconds();
+  return TRUE;
+}
+
+gboolean update_engine_service_emit_status_update(
+    UpdateEngineService* self,
+    gint64 last_checked_time,
+    gdouble progress,
+    const gchar* current_operation,
+    const gchar* new_version,
+    gint64 new_size) {
+  g_signal_emit(self,
+                status_update_signal,
+                0,
+                last_checked_time,
+                progress,
+                current_operation,
+                new_version,
+                new_size);
+  return TRUE;
+}
+
+gboolean update_engine_service_get_prev_version(
+    UpdateEngineService* self,
+    gchar** prev_version,
+    GError **error) {
+  string ver = self->system_state_->update_attempter()->GetPrevVersion();
+  *prev_version = g_strdup(ver.c_str());
+  return TRUE;
+}
diff --git a/dbus_service.h b/dbus_service.h
new file mode 100644
index 0000000..d61e3b5
--- /dev/null
+++ b/dbus_service.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2010 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_DBUS_SERVICE_H_
+#define UPDATE_ENGINE_DBUS_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <glib-object.h>
+
+#include "update_engine/update_attempter.h"
+
+// Type macros:
+#define UPDATE_ENGINE_TYPE_SERVICE (update_engine_service_get_type())
+#define UPDATE_ENGINE_SERVICE(obj)                                      \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), UPDATE_ENGINE_TYPE_SERVICE,        \
+                              UpdateEngineService))
+#define UPDATE_ENGINE_IS_SERVICE(obj)                                   \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), UPDATE_ENGINE_TYPE_SERVICE))
+#define UPDATE_ENGINE_SERVICE_CLASS(klass)                      \
+  (G_TYPE_CHECK_CLASS_CAST((klass), UPDATE_ENGINE_TYPE_SERVICE, \
+                           UpdateEngineService))
+#define UPDATE_ENGINE_IS_SERVICE_CLASS(klass)                           \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), UPDATE_ENGINE_TYPE_SERVICE))
+#define UPDATE_ENGINE_SERVICE_GET_CLASS(obj)                    \
+  (G_TYPE_INSTANCE_GET_CLASS((obj), UPDATE_ENGINE_TYPE_SERVICE, \
+                             UpdateEngineService))
+
+G_BEGIN_DECLS
+
+struct UpdateEngineService {
+  GObject parent_instance;
+
+  chromeos_update_engine::SystemState* system_state_;
+};
+
+struct UpdateEngineServiceClass {
+  GObjectClass parent_class;
+};
+
+UpdateEngineService* update_engine_service_new(void);
+GType update_engine_service_get_type(void);
+
+// Methods
+
+gboolean update_engine_service_attempt_update(UpdateEngineService* self,
+                                              gchar* app_version,
+                                              gchar* omaha_url,
+                                              GError **error);
+
+gboolean update_engine_service_attempt_update_with_flags(
+    UpdateEngineService* self,
+    gchar* app_version,
+    gchar* omaha_url,
+    gint flags_as_int,
+    GError **error);
+
+gboolean update_engine_service_attempt_rollback(UpdateEngineService* self,
+                                                gboolean powerwash,
+                                                GError **error);
+
+// Checks if the system rollback is available by verifying if the secondary
+// system partition is valid and bootable.
+gboolean update_engine_service_can_rollback(
+    UpdateEngineService* self,
+    gboolean* out_can_rollback,
+    GError **error);
+
+// Returns the name of kernel partition that can be rolled back into.
+gboolean update_engine_service_get_rollback_partition(
+  UpdateEngineService* self,
+  gchar** out_rollback_partition_name,
+  GError **error);
+
+// Returns a list of available kernel partitions and whether each of them
+// can be booted from or not.
+gboolean update_engine_service_get_kernel_devices(UpdateEngineService* self,
+                                                  gchar** out_kernel_devices,
+                                                  GError **error);
+
+gboolean update_engine_service_reset_status(UpdateEngineService* self,
+                                            GError **error);
+
+gboolean update_engine_service_get_status(UpdateEngineService* self,
+                                          int64_t* last_checked_time,
+                                          double* progress,
+                                          gchar** current_operation,
+                                          gchar** new_version,
+                                          int64_t* new_size,
+                                          GError **error);
+
+gboolean update_engine_service_reboot_if_needed(UpdateEngineService* self,
+                                                GError **error);
+
+// Changes the current channel of the device to the target channel. If the
+// target channel is a less stable channel than the current channel, then the
+// channel change happens immediately (at the next update check).  If the
+// target channel is a more stable channel, then if is_powerwash_allowed is set
+// to true, then also the change happens immediately but with a powerwash if
+// required. Otherwise, the change takes effect eventually (when the version on
+// the target channel goes above the version number of what the device
+// currently has).
+gboolean update_engine_service_set_channel(UpdateEngineService* self,
+                                           gchar* target_channel,
+                                           gboolean is_powerwash_allowed,
+                                           GError **error);
+
+// If get_current_channel is set to true, populates |channel| with the name of
+// the channel that the device is currently on. Otherwise, it populates it with
+// the name of the channel the device is supposed to be (in case of a pending
+// channel change).
+gboolean update_engine_service_get_channel(UpdateEngineService* self,
+                                           gboolean get_current_channel,
+                                           gchar** channel,
+                                           GError **error);
+
+// Enables or disables the sharing and consuming updates over P2P feature
+// according to the |enabled| argument passed.
+gboolean update_engine_service_set_p2p_update_permission(
+    UpdateEngineService* self,
+    gboolean enabled,
+    GError **error);
+
+// Returns in |enabled| the current value for the P2P enabled setting. This
+// involves both sharing and consuming updates over P2P.
+gboolean update_engine_service_get_p2p_update_permission(
+    UpdateEngineService* self,
+    gboolean* enabled,
+    GError **error);
+
+// If there's no device policy installed, sets the update over cellular networks
+// permission to the |allowed| value. Otherwise, this method returns with an
+// error since this setting is overridden by the applied policy.
+gboolean update_engine_service_set_update_over_cellular_permission(
+    UpdateEngineService* self,
+    gboolean allowed,
+    GError **error);
+
+// Returns the current value of the update over cellular network setting, either
+// forced by the device policy if the device is enrolled or the current user
+// preference otherwise.
+gboolean update_engine_service_get_update_over_cellular_permission(
+    UpdateEngineService* self,
+    gboolean* allowed,
+    GError **error);
+
+// Returns the duration since the last successful update, as the
+// duration on the wallclock. Returns an error if the device has not
+// updated.
+gboolean update_engine_service_get_duration_since_update(
+    UpdateEngineService* self,
+    gint64* out_usec_wallclock,
+    GError **error);
+
+gboolean update_engine_service_emit_status_update(
+    UpdateEngineService* self,
+    gint64 last_checked_time,
+    gdouble progress,
+    const gchar* current_operation,
+    const gchar* new_version,
+    gint64 new_size);
+
+// Returns the version string of OS that was used before the last reboot
+// into an updated version. This is available only when rebooting into an
+// update from previous version, otherwise an empty string is returned.
+gboolean update_engine_service_get_prev_version(
+    UpdateEngineService* self,
+    gchar** prev_version,
+    GError **error);
+
+G_END_DECLS
+
+#endif  // UPDATE_ENGINE_DBUS_SERVICE_H_
diff --git a/dbus_wrapper_interface.h b/dbus_wrapper_interface.h
new file mode 100644
index 0000000..57211c5
--- /dev/null
+++ b/dbus_wrapper_interface.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2010 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_DBUS_WRAPPER_INTERFACE_H_
+#define UPDATE_ENGINE_DBUS_WRAPPER_INTERFACE_H_
+
+// A mockable interface for DBus.
+
+#include <dbus/dbus.h>
+#include <dbus/dbus-glib.h>
+
+#ifndef DBUS_TYPE_G_OBJECT_PATH_ARRAY
+#define DBUS_TYPE_G_OBJECT_PATH_ARRAY \
+  (dbus_g_type_get_collection("GPtrArray", DBUS_TYPE_G_OBJECT_PATH))
+#endif
+
+#ifndef DBUS_TYPE_G_STRING_ARRAY
+#define DBUS_TYPE_G_STRING_ARRAY \
+  (dbus_g_type_get_collection("GPtrArray", G_TYPE_STRING))
+#endif
+
+namespace chromeos_update_engine {
+
+class DBusWrapperInterface {
+ public:
+  virtual ~DBusWrapperInterface() = default;
+
+  // Wraps dbus_g_proxy_new_for_name().
+  virtual DBusGProxy* ProxyNewForName(DBusGConnection* connection,
+                                      const char* name,
+                                      const char* path,
+                                      const char* interface) = 0;
+
+  // Wraps g_object_unref().
+  virtual void ProxyUnref(DBusGProxy* proxy) = 0;
+
+  // Wraps dbus_g_bus_get().
+  virtual DBusGConnection* BusGet(DBusBusType type, GError** error) = 0;
+
+  // Wraps dbus_g_proxy_call(). Since this is a variadic function without a
+  // va_list equivalent, we have to list specific wrappers depending on the
+  // number of input and output arguments, based on the required usage. Note,
+  // however, that we do rely on automatic signature overriding to facilitate
+  // different types of input/output arguments.
+  virtual gboolean ProxyCall_0_1(DBusGProxy* proxy,
+                                 const char* method,
+                                 GError** error,
+                                 GHashTable** out1) = 0;
+
+  virtual gboolean ProxyCall_0_1(DBusGProxy* proxy,
+                                 const char* method,
+                                 GError** error,
+                                 gint* out1) = 0;
+
+  virtual gboolean ProxyCall_1_0(DBusGProxy* proxy,
+                                 const char* method,
+                                 GError** error,
+                                 gint in1) = 0;
+
+  virtual gboolean ProxyCall_3_0(DBusGProxy* proxy,
+                                 const char* method,
+                                 GError** error,
+                                 const char* in1,
+                                 const char* in2,
+                                 const char* in3) = 0;
+
+  // Wrappers for dbus_g_proxy_add_signal() (variadic).
+  virtual void ProxyAddSignal_1(DBusGProxy* proxy,
+                                const char* signal_name,
+                                GType type1) = 0;
+
+  virtual void ProxyAddSignal_2(DBusGProxy* proxy,
+                                const char* signal_name,
+                                GType type1,
+                                GType type2) = 0;
+
+  // Wrapper for dbus_g_proxy_{connect,disconnect}_signal().
+  virtual void ProxyConnectSignal(DBusGProxy* proxy,
+                                  const char* signal_name,
+                                  GCallback handler,
+                                  void* data,
+                                  GClosureNotify free_data_func) = 0;
+  virtual void ProxyDisconnectSignal(DBusGProxy* proxy,
+                                     const char* signal_name,
+                                     GCallback handler,
+                                     void* data) = 0;
+
+  // Wraps dbus_g_connection_get_connection().
+  virtual DBusConnection* ConnectionGetConnection(DBusGConnection* gbus) = 0;
+
+  // Wraps dbus_bus_add_match().
+  virtual void DBusBusAddMatch(DBusConnection* connection,
+                               const char* rule,
+                               DBusError* error) = 0;
+
+  // Wraps dbus_connection_add_filter().
+  virtual dbus_bool_t DBusConnectionAddFilter(
+      DBusConnection* connection,
+      DBusHandleMessageFunction function,
+      void* user_data,
+      DBusFreeFunction free_data_function) = 0;
+
+  // Wraps dbus_connection_remove_filter().
+  virtual void DBusConnectionRemoveFilter(DBusConnection* connection,
+                                          DBusHandleMessageFunction function,
+                                          void* user_data) = 0;
+
+  // Wraps dbus_message_is_signal().
+  virtual dbus_bool_t DBusMessageIsSignal(DBusMessage* message,
+                                          const char* interface,
+                                          const char* signal_name) = 0;
+
+  // Wraps dbus_message_get_args(). Deploys the same approach for handling
+  // variadic arguments as ProxyCall above.
+  virtual dbus_bool_t DBusMessageGetArgs_3(DBusMessage* message,
+                                           DBusError* error,
+                                           char** out1,
+                                           char** out2,
+                                           char** out3) = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DBUS_WRAPPER_INTERFACE_H_
diff --git a/delta_performer.cc b/delta_performer.cc
new file mode 100644
index 0000000..1006f17
--- /dev/null
+++ b/delta_performer.cc
@@ -0,0 +1,1549 @@
+// Copyright (c) 2012 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/delta_performer.h"
+
+#include <endian.h>
+#include <errno.h>
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/data_encoding.h>
+#include <google/protobuf/repeated_field.h>
+
+#include "update_engine/bzip_extent_writer.h"
+#include "update_engine/constants.h"
+#include "update_engine/extent_writer.h"
+#include "update_engine/hardware_interface.h"
+#if USE_MTD
+#include "update_engine/mtd_file_descriptor.h"
+#endif
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+#include "update_engine/update_attempter.h"
+
+using google::protobuf::RepeatedPtrField;
+using std::min;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const uint64_t DeltaPerformer::kDeltaVersionSize = 8;
+const uint64_t DeltaPerformer::kDeltaManifestSizeSize = 8;
+const uint64_t DeltaPerformer::kSupportedMajorPayloadVersion = 1;
+const uint64_t DeltaPerformer::kSupportedMinorPayloadVersion = 2;
+const uint64_t DeltaPerformer::kFullPayloadMinorVersion = 0;
+
+const char DeltaPerformer::kUpdatePayloadPublicKeyPath[] =
+    "/usr/share/update_engine/update-payload-key.pub.pem";
+const unsigned DeltaPerformer::kProgressLogMaxChunks = 10;
+const unsigned DeltaPerformer::kProgressLogTimeoutSeconds = 30;
+const unsigned DeltaPerformer::kProgressDownloadWeight = 50;
+const unsigned DeltaPerformer::kProgressOperationsWeight = 50;
+
+const uint32_t kInPlaceMinorPayloadVersion = 1;
+const uint32_t kSourceMinorPayloadVersion = 2;
+const size_t kDefaultChunkSize = 1024 * 1024;
+
+namespace {
+const int kUpdateStateOperationInvalid = -1;
+const int kMaxResumedUpdateFailures = 10;
+#if USE_MTD
+const int kUbiVolumeAttachTimeout = 5 * 60;
+#endif
+
+FileDescriptorPtr CreateFileDescriptor(const char* path) {
+  FileDescriptorPtr ret;
+#if USE_MTD
+  if (strstr(path, "/dev/ubi") == path) {
+    if (!UbiFileDescriptor::IsUbi(path)) {
+      // The volume might not have been attached at boot time.
+      int volume_no;
+      if (utils::SplitPartitionName(path, nullptr, &volume_no)) {
+        utils::TryAttachingUbiVolume(volume_no, kUbiVolumeAttachTimeout);
+      }
+    }
+    if (UbiFileDescriptor::IsUbi(path)) {
+      LOG(INFO) << path << " is a UBI device.";
+      ret.reset(new UbiFileDescriptor);
+    }
+  } else if (MtdFileDescriptor::IsMtd(path)) {
+    LOG(INFO) << path << " is an MTD device.";
+    ret.reset(new MtdFileDescriptor);
+  } else {
+    LOG(INFO) << path << " is not an MTD nor a UBI device.";
+#endif
+    ret.reset(new EintrSafeFileDescriptor);
+#if USE_MTD
+  }
+#endif
+  return ret;
+}
+
+// Opens path for read/write. On success returns an open FileDescriptor
+// and sets *err to 0. On failure, sets *err to errno and returns nullptr.
+FileDescriptorPtr OpenFile(const char* path, int* err) {
+  FileDescriptorPtr fd = CreateFileDescriptor(path);
+  int mode = O_RDWR;
+#if USE_MTD
+  // On NAND devices, we can either read, or write, but not both. So here we
+  // use O_WRONLY.
+  if (UbiFileDescriptor::IsUbi(path) || MtdFileDescriptor::IsMtd(path)) {
+    mode = O_WRONLY;
+  }
+#endif
+  if (!fd->Open(path, mode, 000)) {
+    *err = errno;
+    PLOG(ERROR) << "Unable to open file " << path;
+    return nullptr;
+  }
+  *err = 0;
+  return fd;
+}
+}  // namespace
+
+
+// Computes the ratio of |part| and |total|, scaled to |norm|, using integer
+// arithmetic.
+static uint64_t IntRatio(uint64_t part, uint64_t total, uint64_t norm) {
+  return part * norm / total;
+}
+
+void DeltaPerformer::LogProgress(const char* message_prefix) {
+  // Format operations total count and percentage.
+  string total_operations_str("?");
+  string completed_percentage_str("");
+  if (num_total_operations_) {
+    total_operations_str = base::StringPrintf("%zu", num_total_operations_);
+    // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+    completed_percentage_str =
+        base::StringPrintf(" (%" PRIu64 "%%)",
+                     IntRatio(next_operation_num_, num_total_operations_,
+                              100));
+  }
+
+  // Format download total count and percentage.
+  size_t payload_size = install_plan_->payload_size;
+  string payload_size_str("?");
+  string downloaded_percentage_str("");
+  if (payload_size) {
+    payload_size_str = base::StringPrintf("%zu", payload_size);
+    // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+    downloaded_percentage_str =
+        base::StringPrintf(" (%" PRIu64 "%%)",
+                     IntRatio(total_bytes_received_, payload_size, 100));
+  }
+
+  LOG(INFO) << (message_prefix ? message_prefix : "") << next_operation_num_
+            << "/" << total_operations_str << " operations"
+            << completed_percentage_str << ", " << total_bytes_received_
+            << "/" << payload_size_str << " bytes downloaded"
+            << downloaded_percentage_str << ", overall progress "
+            << overall_progress_ << "%";
+}
+
+void DeltaPerformer::UpdateOverallProgress(bool force_log,
+                                           const char* message_prefix) {
+  // Compute our download and overall progress.
+  unsigned new_overall_progress = 0;
+  COMPILE_ASSERT(kProgressDownloadWeight + kProgressOperationsWeight == 100,
+                 progress_weight_dont_add_up);
+  // Only consider download progress if its total size is known; otherwise
+  // adjust the operations weight to compensate for the absence of download
+  // progress. Also, make sure to cap the download portion at
+  // kProgressDownloadWeight, in case we end up downloading more than we
+  // initially expected (this indicates a problem, but could generally happen).
+  // TODO(garnold) the correction of operations weight when we do not have the
+  // total payload size, as well as the conditional guard below, should both be
+  // eliminated once we ensure that the payload_size in the install plan is
+  // always given and is non-zero. This currently isn't the case during unit
+  // tests (see chromium-os:37969).
+  size_t payload_size = install_plan_->payload_size;
+  unsigned actual_operations_weight = kProgressOperationsWeight;
+  if (payload_size)
+    new_overall_progress += min(
+        static_cast<unsigned>(IntRatio(total_bytes_received_, payload_size,
+                                       kProgressDownloadWeight)),
+        kProgressDownloadWeight);
+  else
+    actual_operations_weight += kProgressDownloadWeight;
+
+  // Only add completed operations if their total number is known; we definitely
+  // expect an update to have at least one operation, so the expectation is that
+  // this will eventually reach |actual_operations_weight|.
+  if (num_total_operations_)
+    new_overall_progress += IntRatio(next_operation_num_, num_total_operations_,
+                                     actual_operations_weight);
+
+  // Progress ratio cannot recede, unless our assumptions about the total
+  // payload size, total number of operations, or the monotonicity of progress
+  // is breached.
+  if (new_overall_progress < overall_progress_) {
+    LOG(WARNING) << "progress counter receded from " << overall_progress_
+                 << "% down to " << new_overall_progress << "%; this is a bug";
+    force_log = true;
+  }
+  overall_progress_ = new_overall_progress;
+
+  // Update chunk index, log as needed: if forced by called, or we completed a
+  // progress chunk, or a timeout has expired.
+  base::Time curr_time = base::Time::Now();
+  unsigned curr_progress_chunk =
+      overall_progress_ * kProgressLogMaxChunks / 100;
+  if (force_log || curr_progress_chunk > last_progress_chunk_ ||
+      curr_time > forced_progress_log_time_) {
+    forced_progress_log_time_ = curr_time + forced_progress_log_wait_;
+    LogProgress(message_prefix);
+  }
+  last_progress_chunk_ = curr_progress_chunk;
+}
+
+
+size_t DeltaPerformer::CopyDataToBuffer(const char** bytes_p, size_t* count_p,
+                                        size_t max) {
+  const size_t count = *count_p;
+  if (!count)
+    return 0;  // Special case shortcut.
+  size_t read_len = min(count, max - buffer_.size());
+  const char* bytes_start = *bytes_p;
+  const char* bytes_end = bytes_start + read_len;
+  buffer_.insert(buffer_.end(), bytes_start, bytes_end);
+  *bytes_p = bytes_end;
+  *count_p = count - read_len;
+  return read_len;
+}
+
+
+bool DeltaPerformer::HandleOpResult(bool op_result, const char* op_type_name,
+                                    ErrorCode* error) {
+  if (op_result)
+    return true;
+
+  LOG(ERROR) << "Failed to perform " << op_type_name << " operation "
+             << next_operation_num_;
+  *error = ErrorCode::kDownloadOperationExecutionError;
+  return false;
+}
+
+int DeltaPerformer::Open(const char* path, int flags, mode_t mode) {
+  int err;
+  fd_ = OpenFile(path, &err);
+  if (fd_)
+    path_ = path;
+  return -err;
+}
+
+bool DeltaPerformer::OpenKernel(const char* kernel_path) {
+  int err;
+  kernel_fd_ = OpenFile(kernel_path, &err);
+  if (kernel_fd_)
+    kernel_path_ = kernel_path;
+  return static_cast<bool>(kernel_fd_);
+}
+
+bool DeltaPerformer::OpenSourceRootfs(const string& source_path) {
+  int err;
+  source_fd_ = OpenFile(source_path.c_str(), &err);
+  return static_cast<bool>(source_fd_);
+}
+
+bool DeltaPerformer::OpenSourceKernel(const string& source_kernel_path) {
+  int err;
+  source_kernel_fd_ = OpenFile(source_kernel_path.c_str(), &err);
+  return static_cast<bool>(source_kernel_fd_);
+}
+
+int DeltaPerformer::Close() {
+  int err = 0;
+  if (!kernel_fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Unable to close kernel fd:";
+  }
+  if (!fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Unable to close rootfs fd:";
+  }
+  if (source_fd_ && !source_fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Unable to close source rootfs fd:";
+  }
+  if (source_kernel_fd_ && !source_kernel_fd_->Close()) {
+    err = errno;
+    PLOG(ERROR) << "Unable to close source kernel fd:";
+  }
+  LOG_IF(ERROR, !hash_calculator_.Finalize()) << "Unable to finalize the hash.";
+  fd_.reset();  // Set to invalid so that calls to Open() will fail.
+  kernel_fd_.reset();
+  source_fd_.reset();
+  source_kernel_fd_.reset();
+  path_ = "";
+  if (!buffer_.empty()) {
+    LOG(INFO) << "Discarding " << buffer_.size() << " unused downloaded bytes";
+    if (err >= 0)
+      err = 1;
+  }
+  return -err;
+}
+
+namespace {
+
+void LogPartitionInfoHash(const PartitionInfo& info, const string& tag) {
+  string sha256 = chromeos::data_encoding::Base64Encode(info.hash());
+  LOG(INFO) << "PartitionInfo " << tag << " sha256: " << sha256
+            << " size: " << info.size();
+}
+
+void LogPartitionInfo(const DeltaArchiveManifest& manifest) {
+  if (manifest.has_old_kernel_info())
+    LogPartitionInfoHash(manifest.old_kernel_info(), "old_kernel_info");
+  if (manifest.has_old_rootfs_info())
+    LogPartitionInfoHash(manifest.old_rootfs_info(), "old_rootfs_info");
+  if (manifest.has_new_kernel_info())
+    LogPartitionInfoHash(manifest.new_kernel_info(), "new_kernel_info");
+  if (manifest.has_new_rootfs_info())
+    LogPartitionInfoHash(manifest.new_rootfs_info(), "new_rootfs_info");
+}
+
+}  // namespace
+
+uint64_t DeltaPerformer::GetVersionOffset() {
+  // Manifest size is stored right after the magic string and the version.
+  return strlen(kDeltaMagic);
+}
+
+uint64_t DeltaPerformer::GetManifestSizeOffset() {
+  // Manifest size is stored right after the magic string and the version.
+  return strlen(kDeltaMagic) + kDeltaVersionSize;
+}
+
+uint64_t DeltaPerformer::GetManifestOffset() {
+  // Actual manifest begins right after the manifest size field.
+  return GetManifestSizeOffset() + kDeltaManifestSizeSize;
+}
+
+uint64_t DeltaPerformer::GetMetadataSize() const {
+  return metadata_size_;
+}
+
+uint32_t DeltaPerformer::GetMinorVersion() const {
+  if (manifest_.has_minor_version()) {
+    return manifest_.minor_version();
+  } else {
+    return (install_plan_->is_full_update ?
+            kFullPayloadMinorVersion :
+            kSupportedMinorPayloadVersion);
+  }
+}
+
+bool DeltaPerformer::GetManifest(DeltaArchiveManifest* out_manifest_p) const {
+  if (!manifest_parsed_)
+    return false;
+  *out_manifest_p = manifest_;
+  return true;
+}
+
+
+DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
+    const chromeos::Blob& payload, ErrorCode* error) {
+  *error = ErrorCode::kSuccess;
+  const uint64_t manifest_offset = GetManifestOffset();
+  uint64_t manifest_size = (metadata_size_ ?
+                            metadata_size_ - manifest_offset : 0);
+
+  if (!manifest_size) {
+    // Ensure we have data to cover the payload header.
+    if (payload.size() < manifest_offset)
+      return kMetadataParseInsufficientData;
+
+    // Validate the magic string.
+    if (memcmp(payload.data(), kDeltaMagic, strlen(kDeltaMagic)) != 0) {
+      LOG(ERROR) << "Bad payload format -- invalid delta magic.";
+      *error = ErrorCode::kDownloadInvalidMetadataMagicString;
+      return kMetadataParseError;
+    }
+
+    // Extract the payload version from the metadata.
+    uint64_t major_payload_version;
+    COMPILE_ASSERT(sizeof(major_payload_version) == kDeltaVersionSize,
+                   major_payload_version_size_mismatch);
+    memcpy(&major_payload_version,
+           &payload[GetVersionOffset()],
+           kDeltaVersionSize);
+    // switch big endian to host
+    major_payload_version = be64toh(major_payload_version);
+
+    if (major_payload_version != kSupportedMajorPayloadVersion) {
+      LOG(ERROR) << "Bad payload format -- unsupported payload version: "
+          << major_payload_version;
+      *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+      return kMetadataParseError;
+    }
+
+    // Next, parse the manifest size.
+    COMPILE_ASSERT(sizeof(manifest_size) == kDeltaManifestSizeSize,
+                   manifest_size_size_mismatch);
+    memcpy(&manifest_size,
+           &payload[GetManifestSizeOffset()],
+           kDeltaManifestSizeSize);
+    manifest_size = be64toh(manifest_size);  // switch big endian to host
+
+    // If the metadata size is present in install plan, check for it immediately
+    // even before waiting for that many number of bytes to be downloaded in the
+    // payload. This will prevent any attack which relies on us downloading data
+    // beyond the expected metadata size.
+    metadata_size_ = manifest_offset + manifest_size;
+    if (install_plan_->hash_checks_mandatory) {
+      if (install_plan_->metadata_size != metadata_size_) {
+        LOG(ERROR) << "Mandatory metadata size in Omaha response ("
+                   << install_plan_->metadata_size
+                   << ") is missing/incorrect, actual = " << metadata_size_;
+        *error = ErrorCode::kDownloadInvalidMetadataSize;
+        return kMetadataParseError;
+      }
+    }
+  }
+
+  // Now that we have validated the metadata size, we should wait for the full
+  // metadata to be read in before we can parse it.
+  if (payload.size() < metadata_size_)
+    return kMetadataParseInsufficientData;
+
+  // Log whether we validated the size or simply trusting what's in the payload
+  // here. This is logged here (after we received the full metadata data) so
+  // that we just log once (instead of logging n times) if it takes n
+  // DeltaPerformer::Write calls to download the full manifest.
+  if (install_plan_->metadata_size == metadata_size_) {
+    LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
+  } else {
+    // For mandatory-cases, we'd have already returned a kMetadataParseError
+    // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
+    LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
+                 << install_plan_->metadata_size
+                 << ") in Omaha response as validation is not mandatory. "
+                 << "Trusting metadata size in payload = " << metadata_size_;
+    SendUmaStat(ErrorCode::kDownloadInvalidMetadataSize);
+  }
+
+  // We have the full metadata in |payload|. Verify its integrity
+  // and authenticity based on the information we have in Omaha response.
+  *error = ValidateMetadataSignature(payload.data(), metadata_size_);
+  if (*error != ErrorCode::kSuccess) {
+    if (install_plan_->hash_checks_mandatory) {
+      // The autoupdate_CatchBadSignatures test checks for this string
+      // in log-files. Keep in sync.
+      LOG(ERROR) << "Mandatory metadata signature validation failed";
+      return kMetadataParseError;
+    }
+
+    // For non-mandatory cases, just send a UMA stat.
+    LOG(WARNING) << "Ignoring metadata signature validation failures";
+    SendUmaStat(*error);
+    *error = ErrorCode::kSuccess;
+  }
+
+  // The payload metadata is deemed valid, it's safe to parse the protobuf.
+  if (!manifest_.ParseFromArray(&payload[manifest_offset], manifest_size)) {
+    LOG(ERROR) << "Unable to parse manifest in update file.";
+    *error = ErrorCode::kDownloadManifestParseError;
+    return kMetadataParseError;
+  }
+
+  manifest_parsed_ = true;
+  return kMetadataParseSuccess;
+}
+
+// Wrapper around write. Returns true if all requested bytes
+// were written, or false on any error, regardless of progress
+// and stores an action exit code in |error|.
+bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode *error) {
+  *error = ErrorCode::kSuccess;
+
+  const char* c_bytes = reinterpret_cast<const char*>(bytes);
+  system_state_->payload_state()->DownloadProgress(count);
+
+  // Update the total byte downloaded count and the progress logs.
+  total_bytes_received_ += count;
+  UpdateOverallProgress(false, "Completed ");
+
+  while (!manifest_valid_) {
+    // Read data up to the needed limit; this is either the payload header size,
+    // or the full metadata size (once it becomes known).
+    const bool do_read_header = !metadata_size_;
+    CopyDataToBuffer(&c_bytes, &count,
+                     (do_read_header ? GetManifestOffset() :
+                      metadata_size_));
+
+    MetadataParseResult result = ParsePayloadMetadata(buffer_, error);
+    if (result == kMetadataParseError)
+      return false;
+    if (result == kMetadataParseInsufficientData) {
+      // If we just processed the header, make an attempt on the manifest.
+      if (do_read_header && metadata_size_)
+        continue;
+
+      return true;
+    }
+
+    // Checks the integrity of the payload manifest.
+    if ((*error = ValidateManifest()) != ErrorCode::kSuccess)
+      return false;
+    manifest_valid_ = true;
+
+    // Clear the download buffer.
+    DiscardBuffer(false);
+    LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestMetadataSize,
+                                      metadata_size_))
+        << "Unable to save the manifest metadata size.";
+
+    LogPartitionInfo(manifest_);
+    if (!PrimeUpdateState()) {
+      *error = ErrorCode::kDownloadStateInitializationError;
+      LOG(ERROR) << "Unable to prime the update state.";
+      return false;
+    }
+
+    // Open source fds if we have a delta payload with minor version 2.
+    if (!install_plan_->is_full_update &&
+        GetMinorVersion() == kSourceMinorPayloadVersion) {
+      if (!OpenSourceRootfs(install_plan_->source_path)) {
+        LOG(ERROR) << "Unable to open source rootfs partition file "
+                   << install_plan_->source_path;
+        Close();
+        return false;
+      }
+      if (!OpenSourceKernel(install_plan_->kernel_source_path)) {
+        LOG(ERROR) << "Unable to open source kernel partition file "
+                   << install_plan_->kernel_source_path;
+        Close();
+        return false;
+      }
+    }
+
+    num_rootfs_operations_ = manifest_.install_operations_size();
+    num_total_operations_ =
+        num_rootfs_operations_ + manifest_.kernel_install_operations_size();
+    if (next_operation_num_ > 0)
+      UpdateOverallProgress(true, "Resuming after ");
+    LOG(INFO) << "Starting to apply update payload operations";
+  }
+
+  while (next_operation_num_ < num_total_operations_) {
+    // Check if we should cancel the current attempt for any reason.
+    // In this case, *error will have already been populated with the reason
+    // why we're cancelling.
+    if (system_state_->update_attempter()->ShouldCancel(error))
+      return false;
+
+    const bool is_kernel_partition =
+        (next_operation_num_ >= num_rootfs_operations_);
+    const DeltaArchiveManifest_InstallOperation &op =
+        is_kernel_partition ?
+        manifest_.kernel_install_operations(
+            next_operation_num_ - num_rootfs_operations_) :
+        manifest_.install_operations(next_operation_num_);
+
+    CopyDataToBuffer(&c_bytes, &count, op.data_length());
+
+    // Check whether we received all of the next operation's data payload.
+    if (!CanPerformInstallOperation(op))
+      return true;
+
+    // Validate the operation only if the metadata signature is present.
+    // Otherwise, keep the old behavior. This serves as a knob to disable
+    // the validation logic in case we find some regression after rollout.
+    // NOTE: If hash checks are mandatory and if metadata_signature is empty,
+    // we would have already failed in ParsePayloadMetadata method and thus not
+    // even be here. So no need to handle that case again here.
+    if (!install_plan_->metadata_signature.empty()) {
+      // Note: Validate must be called only if CanPerformInstallOperation is
+      // called. Otherwise, we might be failing operations before even if there
+      // isn't sufficient data to compute the proper hash.
+      *error = ValidateOperationHash(op);
+      if (*error != ErrorCode::kSuccess) {
+        if (install_plan_->hash_checks_mandatory) {
+          LOG(ERROR) << "Mandatory operation hash check failed";
+          return false;
+        }
+
+        // For non-mandatory cases, just send a UMA stat.
+        LOG(WARNING) << "Ignoring operation validation errors";
+        SendUmaStat(*error);
+        *error = ErrorCode::kSuccess;
+      }
+    }
+
+    // Makes sure we unblock exit when this operation completes.
+    ScopedTerminatorExitUnblocker exit_unblocker =
+        ScopedTerminatorExitUnblocker();  // Avoids a compiler unused var bug.
+
+    bool op_result;
+    if (op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+        op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ)
+      op_result = HandleOpResult(
+          PerformReplaceOperation(op, is_kernel_partition), "replace", error);
+    else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE)
+      op_result = HandleOpResult(
+          PerformMoveOperation(op, is_kernel_partition), "move", error);
+    else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_BSDIFF)
+      op_result = HandleOpResult(
+          PerformBsdiffOperation(op, is_kernel_partition), "bsdiff", error);
+    else if (op.type() ==
+             DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY)
+      op_result =
+          HandleOpResult(PerformSourceCopyOperation(op, is_kernel_partition),
+                         "source_copy", error);
+    else if (op.type() ==
+             DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF)
+      op_result =
+          HandleOpResult(PerformSourceBsdiffOperation(op, is_kernel_partition),
+                         "source_bsdiff", error);
+    else
+      op_result = HandleOpResult(false, "unknown", error);
+
+    if (!op_result)
+      return false;
+
+    next_operation_num_++;
+    UpdateOverallProgress(false, "Completed ");
+    CheckpointUpdateProgress();
+  }
+  return true;
+}
+
+bool DeltaPerformer::IsManifestValid() {
+  return manifest_valid_;
+}
+
+bool DeltaPerformer::CanPerformInstallOperation(
+    const chromeos_update_engine::DeltaArchiveManifest_InstallOperation&
+    operation) {
+  // Move and source_copy operations don't require any data blob, so they can
+  // always be performed.
+  if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE ||
+      operation.type() ==
+          DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY)
+    return true;
+
+  // See if we have the entire data blob in the buffer
+  if (operation.data_offset() < buffer_offset_) {
+    LOG(ERROR) << "we threw away data it seems?";
+    return false;
+  }
+
+  return (operation.data_offset() + operation.data_length() <=
+          buffer_offset_ + buffer_.size());
+}
+
+bool DeltaPerformer::PerformReplaceOperation(
+    const DeltaArchiveManifest_InstallOperation& operation,
+    bool is_kernel_partition) {
+  CHECK(operation.type() == \
+        DeltaArchiveManifest_InstallOperation_Type_REPLACE || \
+        operation.type() == \
+        DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+
+  // 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.
+  TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+  // Extract the signature message if it's in this operation.
+  ExtractSignatureMessage(operation);
+
+  DirectExtentWriter direct_writer;
+  ZeroPadExtentWriter zero_pad_writer(&direct_writer);
+  unique_ptr<BzipExtentWriter> bzip_writer;
+
+  // Since bzip decompression is optional, we have a variable writer that will
+  // point to one of the ExtentWriter objects above.
+  ExtentWriter* writer = nullptr;
+  if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+    writer = &zero_pad_writer;
+  } else if (operation.type() ==
+             DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+    bzip_writer.reset(new BzipExtentWriter(&zero_pad_writer));
+    writer = bzip_writer.get();
+  } else {
+    NOTREACHED();
+  }
+
+  // Create a vector of extents to pass to the ExtentWriter.
+  vector<Extent> extents;
+  for (int i = 0; i < operation.dst_extents_size(); i++) {
+    extents.push_back(operation.dst_extents(i));
+  }
+
+  FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
+
+  TEST_AND_RETURN_FALSE(writer->Init(fd, extents, block_size_));
+  TEST_AND_RETURN_FALSE(writer->Write(buffer_.data(), operation.data_length()));
+  TEST_AND_RETURN_FALSE(writer->End());
+
+  // Update buffer
+  DiscardBuffer(true);
+  return true;
+}
+
+bool DeltaPerformer::PerformMoveOperation(
+    const DeltaArchiveManifest_InstallOperation& operation,
+    bool is_kernel_partition) {
+  // Calculate buffer size. Note, this function doesn't do a sliding
+  // window to copy in case the source and destination blocks overlap.
+  // If we wanted to do a sliding window, we could program the server
+  // to generate deltas that effectively did a sliding window.
+
+  uint64_t blocks_to_read = 0;
+  for (int i = 0; i < operation.src_extents_size(); i++)
+    blocks_to_read += operation.src_extents(i).num_blocks();
+
+  uint64_t blocks_to_write = 0;
+  for (int i = 0; i < operation.dst_extents_size(); i++)
+    blocks_to_write += operation.dst_extents(i).num_blocks();
+
+  DCHECK_EQ(blocks_to_write, blocks_to_read);
+  chromeos::Blob buf(blocks_to_write * block_size_);
+
+  FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
+
+  // Read in bytes.
+  ssize_t bytes_read = 0;
+  for (int i = 0; i < operation.src_extents_size(); i++) {
+    ssize_t bytes_read_this_iteration = 0;
+    const Extent& extent = operation.src_extents(i);
+    const size_t bytes = extent.num_blocks() * block_size_;
+    TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+    TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
+                                          &buf[bytes_read],
+                                          bytes,
+                                          extent.start_block() * block_size_,
+                                          &bytes_read_this_iteration));
+    TEST_AND_RETURN_FALSE(
+        bytes_read_this_iteration == static_cast<ssize_t>(bytes));
+    bytes_read += bytes_read_this_iteration;
+  }
+
+  // Write bytes out.
+  ssize_t bytes_written = 0;
+  for (int i = 0; i < operation.dst_extents_size(); i++) {
+    const Extent& extent = operation.dst_extents(i);
+    const size_t bytes = extent.num_blocks() * block_size_;
+    TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+    TEST_AND_RETURN_FALSE(utils::PWriteAll(fd,
+                                           &buf[bytes_written],
+                                           bytes,
+                                           extent.start_block() * block_size_));
+    bytes_written += bytes;
+  }
+  DCHECK_EQ(bytes_written, bytes_read);
+  DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.size()));
+  return true;
+}
+
+namespace {
+
+// Takes |extents| and fills an empty vector |blocks| with a block index for
+// each block in |extents|. For example, [(3, 2), (8, 1)] would give [3, 4, 8].
+void ExtentsToBlocks(const RepeatedPtrField<Extent>& extents,
+                     vector<uint64_t>* blocks) {
+  for (Extent ext : extents) {
+    for (uint64_t j = 0; j < ext.num_blocks(); j++)
+      blocks->push_back(ext.start_block() + j);
+  }
+}
+
+// Takes |extents| and returns the number of blocks in those extents.
+uint64_t GetBlockCount(const RepeatedPtrField<Extent>& extents) {
+  uint64_t sum = 0;
+  for (Extent ext : extents) {
+    sum += ext.num_blocks();
+  }
+  return sum;
+}
+
+}  // namespace
+
+bool DeltaPerformer::PerformSourceCopyOperation(
+    const DeltaArchiveManifest_InstallOperation& operation,
+    bool is_kernel_partition) {
+  if (operation.has_src_length())
+    TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+  if (operation.has_dst_length())
+    TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+  uint64_t blocks_to_read = GetBlockCount(operation.src_extents());
+  uint64_t blocks_to_write = GetBlockCount(operation.dst_extents());
+  TEST_AND_RETURN_FALSE(blocks_to_write ==  blocks_to_read);
+
+  // Create vectors of all the individual src/dst blocks.
+  vector<uint64_t> src_blocks;
+  vector<uint64_t> dst_blocks;
+  ExtentsToBlocks(operation.src_extents(), &src_blocks);
+  ExtentsToBlocks(operation.dst_extents(), &dst_blocks);
+  DCHECK_EQ(src_blocks.size(), blocks_to_read);
+  DCHECK_EQ(src_blocks.size(), dst_blocks.size());
+
+  FileDescriptorPtr src_fd =
+      is_kernel_partition ? source_kernel_fd_ : source_fd_;
+  FileDescriptorPtr dst_fd = is_kernel_partition? kernel_fd_ : fd_;
+
+  chromeos::Blob buf(block_size_);
+  ssize_t bytes_read = 0;
+  // Read/write one block at a time.
+  for (uint64_t i = 0; i < blocks_to_read; i++) {
+    ssize_t bytes_read_this_iteration = 0;
+    uint64_t src_block = src_blocks[i];
+    uint64_t dst_block = dst_blocks[i];
+
+    // Read in bytes.
+    TEST_AND_RETURN_FALSE(
+        utils::PReadAll(src_fd,
+                        buf.data(),
+                        block_size_,
+                        src_block * block_size_,
+                        &bytes_read_this_iteration));
+
+    // Write bytes out.
+    TEST_AND_RETURN_FALSE(
+        utils::PWriteAll(dst_fd,
+                         buf.data(),
+                         block_size_,
+                         dst_block * block_size_));
+
+    bytes_read += bytes_read_this_iteration;
+    TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
+                          static_cast<ssize_t>(block_size_));
+  }
+  DCHECK_EQ(bytes_read, static_cast<ssize_t>(blocks_to_read * block_size_));
+  return true;
+}
+
+bool DeltaPerformer::ExtentsToBsdiffPositionsString(
+    const RepeatedPtrField<Extent>& extents,
+    uint64_t block_size,
+    uint64_t full_length,
+    string* positions_string) {
+  string ret;
+  uint64_t length = 0;
+  for (int i = 0; i < extents.size(); i++) {
+    Extent extent = extents.Get(i);
+    int64_t start = extent.start_block() * block_size;
+    uint64_t this_length = min(full_length - length,
+                               extent.num_blocks() * block_size);
+    ret += base::StringPrintf("%" PRIi64 ":%" PRIu64 ",", start, this_length);
+    length += this_length;
+  }
+  TEST_AND_RETURN_FALSE(length == full_length);
+  if (!ret.empty())
+    ret.resize(ret.size() - 1);  // Strip trailing comma off
+  *positions_string = ret;
+  return true;
+}
+
+bool DeltaPerformer::PerformBsdiffOperation(
+    const DeltaArchiveManifest_InstallOperation& operation,
+    bool is_kernel_partition) {
+  // 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.
+  TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+  string input_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+                                                       block_size_,
+                                                       operation.src_length(),
+                                                       &input_positions));
+  string output_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+                                                       block_size_,
+                                                       operation.dst_length(),
+                                                       &output_positions));
+
+  string temp_filename;
+  TEST_AND_RETURN_FALSE(utils::MakeTempFile("/tmp/au_patch.XXXXXX",
+                                            &temp_filename,
+                                            nullptr));
+  ScopedPathUnlinker path_unlinker(temp_filename);
+  {
+    int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+    ScopedFdCloser fd_closer(&fd);
+    TEST_AND_RETURN_FALSE(
+        utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+  }
+
+  // Update the buffer to release the patch data memory as soon as the patch
+  // file is written out.
+  DiscardBuffer(true);
+
+  const string& path = is_kernel_partition ? kernel_path_ : path_;
+  vector<string> cmd{kBspatchPath, path, path, temp_filename,
+                     input_positions, output_positions};
+
+  int return_code = 0;
+  TEST_AND_RETURN_FALSE(
+      Subprocess::SynchronousExecFlags(cmd,
+                                       G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
+                                       &return_code,
+                                       nullptr));
+  TEST_AND_RETURN_FALSE(return_code == 0);
+
+  if (operation.dst_length() % block_size_) {
+    // Zero out rest of final block.
+    // TODO(adlr): build this into bspatch; it's more efficient that way.
+    const Extent& last_extent =
+        operation.dst_extents(operation.dst_extents_size() - 1);
+    const uint64_t end_byte =
+        (last_extent.start_block() + last_extent.num_blocks()) * block_size_;
+    const uint64_t begin_byte =
+        end_byte - (block_size_ - operation.dst_length() % block_size_);
+    chromeos::Blob zeros(end_byte - begin_byte);
+    FileDescriptorPtr fd = is_kernel_partition ? kernel_fd_ : fd_;
+    TEST_AND_RETURN_FALSE(
+        utils::PWriteAll(fd, zeros.data(), end_byte - begin_byte, begin_byte));
+  }
+  return true;
+}
+
+bool DeltaPerformer::PerformSourceBsdiffOperation(
+    const DeltaArchiveManifest_InstallOperation& operation,
+    bool is_kernel_partition) {
+  // 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.
+  TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+  if (operation.has_src_length())
+    TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+  if (operation.has_dst_length())
+    TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+  string input_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+                                                       block_size_,
+                                                       operation.src_length(),
+                                                       &input_positions));
+  string output_positions;
+  TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+                                                       block_size_,
+                                                       operation.dst_length(),
+                                                       &output_positions));
+
+  string temp_filename;
+  TEST_AND_RETURN_FALSE(utils::MakeTempFile("/tmp/au_patch.XXXXXX",
+                                            &temp_filename,
+                                            nullptr));
+  ScopedPathUnlinker path_unlinker(temp_filename);
+  {
+    int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+    ScopedFdCloser fd_closer(&fd);
+    TEST_AND_RETURN_FALSE(
+        utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+  }
+
+  // Update the buffer to release the patch data memory as soon as the patch
+  // file is written out.
+  DiscardBuffer(true);
+
+  const string& src_path = is_kernel_partition ?
+                           install_plan_->kernel_source_path :
+                           install_plan_->source_path;
+  const string& dst_path = is_kernel_partition ? kernel_path_ : path_;
+  vector<string> cmd{kBspatchPath, src_path, dst_path, temp_filename,
+                     input_positions, output_positions};
+
+  int return_code = 0;
+  TEST_AND_RETURN_FALSE(
+      Subprocess::SynchronousExecFlags(cmd,
+                                       G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
+                                       &return_code,
+                                       nullptr));
+  TEST_AND_RETURN_FALSE(return_code == 0);
+  return true;
+}
+
+bool DeltaPerformer::ExtractSignatureMessage(
+    const DeltaArchiveManifest_InstallOperation& operation) {
+  if (operation.type() != DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+      !manifest_.has_signatures_offset() ||
+      manifest_.signatures_offset() != operation.data_offset()) {
+    return false;
+  }
+  TEST_AND_RETURN_FALSE(manifest_.has_signatures_size() &&
+                        manifest_.signatures_size() == operation.data_length());
+  TEST_AND_RETURN_FALSE(signatures_message_data_.empty());
+  TEST_AND_RETURN_FALSE(buffer_offset_ == manifest_.signatures_offset());
+  TEST_AND_RETURN_FALSE(buffer_.size() >= manifest_.signatures_size());
+  signatures_message_data_.assign(
+      buffer_.begin(),
+      buffer_.begin() + manifest_.signatures_size());
+
+  // Save the signature blob because if the update is interrupted after the
+  // download phase we don't go through this path anymore. Some alternatives to
+  // consider:
+  //
+  // 1. On resume, re-download the signature blob from the server and re-verify
+  // it.
+  //
+  // 2. Verify the signature as soon as it's received and don't checkpoint the
+  // blob and the signed sha-256 context.
+  LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateStateSignatureBlob,
+                                     string(signatures_message_data_.begin(),
+                                            signatures_message_data_.end())))
+      << "Unable to store the signature blob.";
+  // The hash of all data consumed so far should be verified against the signed
+  // hash.
+  signed_hash_context_ = hash_calculator_.GetContext();
+  LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateStateSignedSHA256Context,
+                                     signed_hash_context_))
+      << "Unable to store the signed hash context.";
+  LOG(INFO) << "Extracted signature data of size "
+            << manifest_.signatures_size() << " at "
+            << manifest_.signatures_offset();
+  return true;
+}
+
+bool DeltaPerformer::GetPublicKeyFromResponse(base::FilePath *out_tmp_key) {
+  if (system_state_->hardware()->IsOfficialBuild() ||
+      utils::FileExists(public_key_path_.c_str()) ||
+      install_plan_->public_key_rsa.empty())
+    return false;
+
+  if (!utils::DecodeAndStoreBase64String(install_plan_->public_key_rsa,
+                                         out_tmp_key))
+    return false;
+
+  return true;
+}
+
+ErrorCode DeltaPerformer::ValidateMetadataSignature(
+    const void* metadata, uint64_t metadata_size) {
+
+  if (install_plan_->metadata_signature.empty()) {
+    if (install_plan_->hash_checks_mandatory) {
+      LOG(ERROR) << "Missing mandatory metadata signature in Omaha response";
+      return ErrorCode::kDownloadMetadataSignatureMissingError;
+    }
+
+    // For non-mandatory cases, just send a UMA stat.
+    LOG(WARNING) << "Cannot validate metadata as the signature is empty";
+    SendUmaStat(ErrorCode::kDownloadMetadataSignatureMissingError);
+    return ErrorCode::kSuccess;
+  }
+
+  // Convert base64-encoded signature to raw bytes.
+  chromeos::Blob metadata_signature;
+  if (!chromeos::data_encoding::Base64Decode(install_plan_->metadata_signature,
+                                             &metadata_signature)) {
+    LOG(ERROR) << "Unable to decode base64 metadata signature: "
+               << install_plan_->metadata_signature;
+    return ErrorCode::kDownloadMetadataSignatureError;
+  }
+
+  // See if we should use the public RSA key in the Omaha response.
+  base::FilePath path_to_public_key(public_key_path_);
+  base::FilePath tmp_key;
+  if (GetPublicKeyFromResponse(&tmp_key))
+    path_to_public_key = tmp_key;
+  ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+  if (tmp_key.empty())
+    tmp_key_remover.set_should_remove(false);
+
+  LOG(INFO) << "Verifying metadata hash signature using public key: "
+            << path_to_public_key.value();
+
+  chromeos::Blob expected_metadata_hash;
+  if (!PayloadVerifier::GetRawHashFromSignature(metadata_signature,
+                                                path_to_public_key.value(),
+                                                &expected_metadata_hash)) {
+    LOG(ERROR) << "Unable to compute expected hash from metadata signature";
+    return ErrorCode::kDownloadMetadataSignatureError;
+  }
+
+  OmahaHashCalculator metadata_hasher;
+  metadata_hasher.Update(metadata, metadata_size);
+  if (!metadata_hasher.Finalize()) {
+    LOG(ERROR) << "Unable to compute actual hash of manifest";
+    return ErrorCode::kDownloadMetadataSignatureVerificationError;
+  }
+
+  chromeos::Blob calculated_metadata_hash = metadata_hasher.raw_hash();
+  PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);
+  if (calculated_metadata_hash.empty()) {
+    LOG(ERROR) << "Computed actual hash of metadata is empty.";
+    return ErrorCode::kDownloadMetadataSignatureVerificationError;
+  }
+
+  if (calculated_metadata_hash != expected_metadata_hash) {
+    LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
+    utils::HexDumpVector(expected_metadata_hash);
+    LOG(ERROR) << "Calculated hash = ";
+    utils::HexDumpVector(calculated_metadata_hash);
+    return ErrorCode::kDownloadMetadataSignatureMismatch;
+  }
+
+  // The autoupdate_CatchBadSignatures test checks for this string in
+  // log-files. Keep in sync.
+  LOG(INFO) << "Metadata hash signature matches value in Omaha response.";
+  return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateManifest() {
+  // Perform assorted checks to sanity check the manifest, make sure it
+  // matches data from other sources, and that it is a supported version.
+  //
+  // TODO(garnold) in general, the presence of an old partition hash should be
+  // the sole indicator for a delta update, as we would generally like update
+  // payloads to be self contained and not assume an Omaha response to tell us
+  // that. However, since this requires some massive reengineering of the update
+  // flow (making filesystem copying happen conditionally only *after*
+  // downloading and parsing of the update manifest) we'll put it off for now.
+  // See chromium-os:7597 for further discussion.
+  if (install_plan_->is_full_update) {
+    if (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info()) {
+      LOG(ERROR) << "Purported full payload contains old partition "
+                    "hash(es), aborting update";
+      return ErrorCode::kPayloadMismatchedType;
+    }
+
+    if (manifest_.minor_version() != kFullPayloadMinorVersion) {
+      LOG(ERROR) << "Manifest contains minor version "
+                 << manifest_.minor_version()
+                 << ", but all full payloads should have version "
+                 << kFullPayloadMinorVersion << ".";
+      return ErrorCode::kUnsupportedMinorPayloadVersion;
+    }
+  } else {
+    if (manifest_.minor_version() != supported_minor_version_) {
+      LOG(ERROR) << "Manifest contains minor version "
+                 << manifest_.minor_version()
+                 << " not the supported "
+                 << supported_minor_version_;
+      return ErrorCode::kUnsupportedMinorPayloadVersion;
+    }
+  }
+
+  // TODO(garnold) we should be adding more and more manifest checks, such as
+  // partition boundaries etc (see chromium-os:37661).
+
+  return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateOperationHash(
+    const DeltaArchiveManifest_InstallOperation& operation) {
+
+  if (!operation.data_sha256_hash().size()) {
+    if (!operation.data_length()) {
+      // Operations that do not have any data blob won't have any operation hash
+      // either. So, these operations are always considered validated since the
+      // metadata that contains all the non-data-blob portions of the operation
+      // has already been validated. This is true for both HTTP and HTTPS cases.
+      return ErrorCode::kSuccess;
+    }
+
+    // No hash is present for an operation that has data blobs. This shouldn't
+    // happen normally for any client that has this code, because the
+    // corresponding update should have been produced with the operation
+    // hashes. So if it happens it means either we've turned operation hash
+    // generation off in DeltaDiffGenerator or it's a regression of some sort.
+    // One caveat though: The last operation is a dummy signature operation
+    // that doesn't have a hash at the time the manifest is created. So we
+    // should not complaint about that operation. This operation can be
+    // recognized by the fact that it's offset is mentioned in the manifest.
+    if (manifest_.signatures_offset() &&
+        manifest_.signatures_offset() == operation.data_offset()) {
+      LOG(INFO) << "Skipping hash verification for signature operation "
+                << next_operation_num_ + 1;
+    } else {
+      if (install_plan_->hash_checks_mandatory) {
+        LOG(ERROR) << "Missing mandatory operation hash for operation "
+                   << next_operation_num_ + 1;
+        return ErrorCode::kDownloadOperationHashMissingError;
+      }
+
+      // For non-mandatory cases, just send a UMA stat.
+      LOG(WARNING) << "Cannot validate operation " << next_operation_num_ + 1
+                   << " as there's no operation hash in manifest";
+      SendUmaStat(ErrorCode::kDownloadOperationHashMissingError);
+    }
+    return ErrorCode::kSuccess;
+  }
+
+  chromeos::Blob expected_op_hash;
+  expected_op_hash.assign(operation.data_sha256_hash().data(),
+                          (operation.data_sha256_hash().data() +
+                           operation.data_sha256_hash().size()));
+
+  OmahaHashCalculator operation_hasher;
+  operation_hasher.Update(buffer_.data(), operation.data_length());
+  if (!operation_hasher.Finalize()) {
+    LOG(ERROR) << "Unable to compute actual hash of operation "
+               << next_operation_num_;
+    return ErrorCode::kDownloadOperationHashVerificationError;
+  }
+
+  chromeos::Blob calculated_op_hash = operation_hasher.raw_hash();
+  if (calculated_op_hash != expected_op_hash) {
+    LOG(ERROR) << "Hash verification failed for operation "
+               << next_operation_num_ << ". Expected hash = ";
+    utils::HexDumpVector(expected_op_hash);
+    LOG(ERROR) << "Calculated hash over " << operation.data_length()
+               << " bytes at offset: " << operation.data_offset() << " = ";
+    utils::HexDumpVector(calculated_op_hash);
+    return ErrorCode::kDownloadOperationHashMismatch;
+  }
+
+  return ErrorCode::kSuccess;
+}
+
+#define TEST_AND_RETURN_VAL(_retval, _condition)                \
+  do {                                                          \
+    if (!(_condition)) {                                        \
+      LOG(ERROR) << "VerifyPayload failure: " << #_condition;   \
+      return _retval;                                           \
+    }                                                           \
+  } while (0);
+
+ErrorCode DeltaPerformer::VerifyPayload(
+    const string& update_check_response_hash,
+    const uint64_t update_check_response_size) {
+
+  // See if we should use the public RSA key in the Omaha response.
+  base::FilePath path_to_public_key(public_key_path_);
+  base::FilePath tmp_key;
+  if (GetPublicKeyFromResponse(&tmp_key))
+    path_to_public_key = tmp_key;
+  ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+  if (tmp_key.empty())
+    tmp_key_remover.set_should_remove(false);
+
+  LOG(INFO) << "Verifying payload using public key: "
+            << path_to_public_key.value();
+
+  // Verifies the download size.
+  TEST_AND_RETURN_VAL(ErrorCode::kPayloadSizeMismatchError,
+                      update_check_response_size ==
+                      metadata_size_ + buffer_offset_);
+
+  // Verifies the payload hash.
+  const string& payload_hash_data = hash_calculator_.hash();
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
+                      !payload_hash_data.empty());
+  TEST_AND_RETURN_VAL(ErrorCode::kPayloadHashMismatchError,
+                      payload_hash_data == update_check_response_hash);
+
+  // Verifies the signed payload hash.
+  if (!utils::FileExists(path_to_public_key.value().c_str())) {
+    LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
+    return ErrorCode::kSuccess;
+  }
+  TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError,
+                      !signatures_message_data_.empty());
+  chromeos::Blob signed_hash_data;
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+                      PayloadVerifier::VerifySignature(
+                          signatures_message_data_,
+                          path_to_public_key.value(),
+                          &signed_hash_data));
+  OmahaHashCalculator signed_hasher;
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+                      signed_hasher.SetContext(signed_hash_context_));
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+                      signed_hasher.Finalize());
+  chromeos::Blob hash_data = signed_hasher.raw_hash();
+  PayloadVerifier::PadRSA2048SHA256Hash(&hash_data);
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+                      !hash_data.empty());
+  if (hash_data != signed_hash_data) {
+    // The autoupdate_CatchBadSignatures test checks for this string
+    // in log-files. Keep in sync.
+    LOG(ERROR) << "Public key verification failed, thus update failed. "
+        "Attached Signature:";
+    utils::HexDumpVector(signed_hash_data);
+    LOG(ERROR) << "Computed Signature:";
+    utils::HexDumpVector(hash_data);
+    return ErrorCode::kDownloadPayloadPubKeyVerificationError;
+  }
+
+  LOG(INFO) << "Payload hash matches value in payload.";
+
+  // At this point, we are guaranteed to have downloaded a full payload, i.e
+  // the one whose size matches the size mentioned in Omaha response. If any
+  // errors happen after this, it's likely a problem with the payload itself or
+  // the state of the system and not a problem with the URL or network.  So,
+  // indicate that to the payload state so that AU can backoff appropriately.
+  system_state_->payload_state()->DownloadComplete();
+
+  return ErrorCode::kSuccess;
+}
+
+bool DeltaPerformer::GetNewPartitionInfo(uint64_t* kernel_size,
+                                         chromeos::Blob* kernel_hash,
+                                         uint64_t* rootfs_size,
+                                         chromeos::Blob* rootfs_hash) {
+  TEST_AND_RETURN_FALSE(manifest_valid_ &&
+                        manifest_.has_new_kernel_info() &&
+                        manifest_.has_new_rootfs_info());
+  *kernel_size = manifest_.new_kernel_info().size();
+  *rootfs_size = manifest_.new_rootfs_info().size();
+  chromeos::Blob new_kernel_hash(manifest_.new_kernel_info().hash().begin(),
+                                 manifest_.new_kernel_info().hash().end());
+  chromeos::Blob new_rootfs_hash(manifest_.new_rootfs_info().hash().begin(),
+                                 manifest_.new_rootfs_info().hash().end());
+  kernel_hash->swap(new_kernel_hash);
+  rootfs_hash->swap(new_rootfs_hash);
+  return true;
+}
+
+namespace {
+void LogVerifyError(bool is_kern,
+                    const string& local_hash,
+                    const string& expected_hash) {
+  const char* type = is_kern ? "kernel" : "rootfs";
+  LOG(ERROR) << "This is a server-side error due to "
+             << "mismatched delta update image!";
+  LOG(ERROR) << "The delta I've been given contains a " << type << " delta "
+             << "update that must be applied over a " << type << " with "
+             << "a specific checksum, but the " << type << " we're starting "
+             << "with doesn't have that checksum! This means that "
+             << "the delta I've been given doesn't match my existing "
+             << "system. The " << type << " partition I have has hash: "
+             << local_hash << " but the update expected me to have "
+             << expected_hash << " .";
+  if (is_kern) {
+    LOG(INFO) << "To get the checksum of a kernel partition on a "
+              << "booted machine, run this command (change /dev/sda2 "
+              << "as needed): dd if=/dev/sda2 bs=1M 2>/dev/null | "
+              << "openssl dgst -sha256 -binary | openssl base64";
+  } else {
+    LOG(INFO) << "To get the checksum of a rootfs partition on a "
+              << "booted machine, run this command (change /dev/sda3 "
+              << "as needed): dd if=/dev/sda3 bs=1M count=$(( "
+              << "$(dumpe2fs /dev/sda3  2>/dev/null | grep 'Block count' "
+              << "| sed 's/[^0-9]*//') / 256 )) | "
+              << "openssl dgst -sha256 -binary | openssl base64";
+  }
+  LOG(INFO) << "To get the checksum of partitions in a bin file, "
+            << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
+}
+
+string StringForHashBytes(const void* bytes, size_t size) {
+  return chromeos::data_encoding::Base64Encode(bytes, size);
+}
+}  // namespace
+
+bool DeltaPerformer::VerifySourcePartitions() {
+  LOG(INFO) << "Verifying source partitions.";
+  CHECK(manifest_valid_);
+  CHECK(install_plan_);
+  if (manifest_.has_old_kernel_info()) {
+    const PartitionInfo& info = manifest_.old_kernel_info();
+    bool valid =
+        !install_plan_->source_kernel_hash.empty() &&
+        install_plan_->source_kernel_hash.size() == info.hash().size() &&
+        memcmp(install_plan_->source_kernel_hash.data(),
+               info.hash().data(),
+               install_plan_->source_kernel_hash.size()) == 0;
+    if (!valid) {
+      LogVerifyError(true,
+                     StringForHashBytes(
+                         install_plan_->source_kernel_hash.data(),
+                         install_plan_->source_kernel_hash.size()),
+                     StringForHashBytes(info.hash().data(),
+                                        info.hash().size()));
+    }
+    TEST_AND_RETURN_FALSE(valid);
+  }
+  if (manifest_.has_old_rootfs_info()) {
+    const PartitionInfo& info = manifest_.old_rootfs_info();
+    bool valid =
+        !install_plan_->source_rootfs_hash.empty() &&
+        install_plan_->source_rootfs_hash.size() == info.hash().size() &&
+        memcmp(install_plan_->source_rootfs_hash.data(),
+               info.hash().data(),
+               install_plan_->source_rootfs_hash.size()) == 0;
+    if (!valid) {
+      LogVerifyError(false,
+                     StringForHashBytes(
+                         install_plan_->source_rootfs_hash.data(),
+                         install_plan_->source_rootfs_hash.size()),
+                     StringForHashBytes(info.hash().data(),
+                                        info.hash().size()));
+    }
+    TEST_AND_RETURN_FALSE(valid);
+  }
+  return true;
+}
+
+void DeltaPerformer::DiscardBuffer(bool do_advance_offset) {
+  // Update the buffer offset.
+  if (do_advance_offset)
+    buffer_offset_ += buffer_.size();
+
+  // Hash the content.
+  hash_calculator_.Update(buffer_.data(), buffer_.size());
+
+  // Swap content with an empty vector to ensure that all memory is released.
+  chromeos::Blob().swap(buffer_);
+}
+
+bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs,
+                                     string update_check_response_hash) {
+  int64_t next_operation = kUpdateStateOperationInvalid;
+  if (!(prefs->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) &&
+        next_operation != kUpdateStateOperationInvalid &&
+        next_operation > 0))
+    return false;
+
+  string interrupted_hash;
+  if (!(prefs->GetString(kPrefsUpdateCheckResponseHash, &interrupted_hash) &&
+        !interrupted_hash.empty() &&
+        interrupted_hash == update_check_response_hash))
+    return false;
+
+  int64_t resumed_update_failures;
+  if (!(prefs->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)
+        && resumed_update_failures > kMaxResumedUpdateFailures))
+    return false;
+
+  // Sanity check the rest.
+  int64_t next_data_offset = -1;
+  if (!(prefs->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset) &&
+        next_data_offset >= 0))
+    return false;
+
+  string sha256_context;
+  if (!(prefs->GetString(kPrefsUpdateStateSHA256Context, &sha256_context) &&
+        !sha256_context.empty()))
+    return false;
+
+  int64_t manifest_metadata_size = 0;
+  if (!(prefs->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size) &&
+        manifest_metadata_size > 0))
+    return false;
+
+  return true;
+}
+
+bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) {
+  TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation,
+                                        kUpdateStateOperationInvalid));
+  if (!quick) {
+    prefs->SetString(kPrefsUpdateCheckResponseHash, "");
+    prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
+    prefs->SetInt64(kPrefsUpdateStateNextDataLength, 0);
+    prefs->SetString(kPrefsUpdateStateSHA256Context, "");
+    prefs->SetString(kPrefsUpdateStateSignedSHA256Context, "");
+    prefs->SetString(kPrefsUpdateStateSignatureBlob, "");
+    prefs->SetInt64(kPrefsManifestMetadataSize, -1);
+    prefs->SetInt64(kPrefsResumedUpdateFailures, 0);
+  }
+  return true;
+}
+
+bool DeltaPerformer::CheckpointUpdateProgress() {
+  Terminator::set_exit_blocked(true);
+  if (last_updated_buffer_offset_ != buffer_offset_) {
+    // Resets the progress in case we die in the middle of the state update.
+    ResetUpdateProgress(prefs_, true);
+    TEST_AND_RETURN_FALSE(
+        prefs_->SetString(kPrefsUpdateStateSHA256Context,
+                          hash_calculator_.GetContext()));
+    TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataOffset,
+                                           buffer_offset_));
+    last_updated_buffer_offset_ = buffer_offset_;
+
+    if (next_operation_num_ < num_total_operations_) {
+      const bool is_kernel_partition =
+          next_operation_num_ >= num_rootfs_operations_;
+      const DeltaArchiveManifest_InstallOperation &op =
+          is_kernel_partition ?
+          manifest_.kernel_install_operations(
+              next_operation_num_ - num_rootfs_operations_) :
+          manifest_.install_operations(next_operation_num_);
+      TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+                                             op.data_length()));
+    } else {
+      TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+                                             0));
+    }
+  }
+  TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextOperation,
+                                         next_operation_num_));
+  return true;
+}
+
+bool DeltaPerformer::PrimeUpdateState() {
+  CHECK(manifest_valid_);
+  block_size_ = manifest_.block_size();
+
+  int64_t next_operation = kUpdateStateOperationInvalid;
+  if (!prefs_->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) ||
+      next_operation == kUpdateStateOperationInvalid ||
+      next_operation <= 0) {
+    // Initiating a new update, no more state needs to be initialized.
+    TEST_AND_RETURN_FALSE(VerifySourcePartitions());
+    return true;
+  }
+  next_operation_num_ = next_operation;
+
+  // Resuming an update -- load the rest of the update state.
+  int64_t next_data_offset = -1;
+  TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsUpdateStateNextDataOffset,
+                                         &next_data_offset) &&
+                        next_data_offset >= 0);
+  buffer_offset_ = next_data_offset;
+
+  // The signed hash context and the signature blob may be empty if the
+  // interrupted update didn't reach the signature.
+  prefs_->GetString(kPrefsUpdateStateSignedSHA256Context,
+                    &signed_hash_context_);
+  string signature_blob;
+  if (prefs_->GetString(kPrefsUpdateStateSignatureBlob, &signature_blob)) {
+    signatures_message_data_.assign(signature_blob.begin(),
+                                    signature_blob.end());
+  }
+
+  string hash_context;
+  TEST_AND_RETURN_FALSE(prefs_->GetString(kPrefsUpdateStateSHA256Context,
+                                          &hash_context) &&
+                        hash_calculator_.SetContext(hash_context));
+
+  int64_t manifest_metadata_size = 0;
+  TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsManifestMetadataSize,
+                                         &manifest_metadata_size) &&
+                        manifest_metadata_size > 0);
+  metadata_size_ = manifest_metadata_size;
+
+  // Advance the download progress to reflect what doesn't need to be
+  // re-downloaded.
+  total_bytes_received_ += buffer_offset_;
+
+  // Speculatively count the resume as a failure.
+  int64_t resumed_update_failures;
+  if (prefs_->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)) {
+    resumed_update_failures++;
+  } else {
+    resumed_update_failures = 1;
+  }
+  prefs_->SetInt64(kPrefsResumedUpdateFailures, resumed_update_failures);
+  return true;
+}
+
+void DeltaPerformer::SendUmaStat(ErrorCode code) {
+  utils::SendErrorCodeToUma(system_state_, code);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/delta_performer.h b/delta_performer.h
new file mode 100644
index 0000000..ba479af
--- /dev/null
+++ b/delta_performer.h
@@ -0,0 +1,401 @@
+// Copyright (c) 2010 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_DELTA_PERFORMER_H_
+#define UPDATE_ENGINE_DELTA_PERFORMER_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <chromeos/secure_blob.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/file_descriptor.h"
+#include "update_engine/file_writer.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The minor version used by the in-place delta generator algorithm.
+extern const uint32_t kInPlaceMinorPayloadVersion;
+
+// The minor version used by the A to B delta generator algorithm.
+extern const uint32_t kSourceMinorPayloadVersion;
+
+// Chunk size used for payloads during test.
+extern const size_t kDefaultChunkSize;
+
+class PrefsInterface;
+
+// This class performs the actions in a delta update synchronously. The delta
+// update itself should be passed in in chunks as it is received.
+
+class DeltaPerformer : public FileWriter {
+ public:
+  enum MetadataParseResult {
+    kMetadataParseSuccess,
+    kMetadataParseError,
+    kMetadataParseInsufficientData,
+  };
+
+  static const uint64_t kDeltaVersionSize;
+  static const uint64_t kDeltaManifestSizeSize;
+  static const uint64_t kSupportedMajorPayloadVersion;
+  static const uint64_t kSupportedMinorPayloadVersion;
+  static const uint64_t kFullPayloadMinorVersion;
+  static const char kUpdatePayloadPublicKeyPath[];
+
+  // Defines the granularity of progress logging in terms of how many "completed
+  // chunks" we want to report at the most.
+  static const unsigned kProgressLogMaxChunks;
+  // Defines a timeout since the last progress was logged after which we want to
+  // force another log message (even if the current chunk was not completed).
+  static const unsigned kProgressLogTimeoutSeconds;
+  // These define the relative weights (0-100) we give to the different work
+  // components associated with an update when computing an overall progress.
+  // Currently they include the download progress and the number of completed
+  // operations. They must add up to one hundred (100).
+  static const unsigned kProgressDownloadWeight;
+  static const unsigned kProgressOperationsWeight;
+
+  DeltaPerformer(PrefsInterface* prefs,
+                 SystemState* system_state,
+                 InstallPlan* install_plan)
+      : prefs_(prefs),
+        system_state_(system_state),
+        install_plan_(install_plan),
+        fd_(nullptr),
+        kernel_fd_(nullptr),
+        source_fd_(nullptr),
+        source_kernel_fd_(nullptr),
+        manifest_parsed_(false),
+        manifest_valid_(false),
+        metadata_size_(0),
+        next_operation_num_(0),
+        buffer_offset_(0),
+        last_updated_buffer_offset_(kuint64max),
+        block_size_(0),
+        public_key_path_(kUpdatePayloadPublicKeyPath),
+        total_bytes_received_(0),
+        num_rootfs_operations_(0),
+        num_total_operations_(0),
+        overall_progress_(0),
+        last_progress_chunk_(0),
+        forced_progress_log_wait_(
+            base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)),
+        supported_minor_version_(kSupportedMinorPayloadVersion) {}
+
+  // Opens the kernel. Should be called before or after Open(), but before
+  // Write(). The kernel file will be close()d when Close() is called.
+  bool OpenKernel(const char* kernel_path);
+
+  // Opens the source partition. The file will be closed when Close() is called.
+  bool OpenSourceRootfs(const std::string& kernel_path);
+
+  // Opens the source kernel. The file will be closed when Close() is called.
+  bool OpenSourceKernel(const std::string& source_kernel_path);
+
+  // flags and mode ignored. Once Close()d, a DeltaPerformer can't be
+  // Open()ed again.
+  int Open(const char* path, int flags, mode_t mode) override;
+
+  // FileWriter's Write implementation where caller doesn't care about
+  // error codes.
+  bool Write(const void* bytes, size_t count) override {
+    ErrorCode error;
+    return Write(bytes, count, &error);
+  }
+
+  // FileWriter's Write implementation that returns a more specific |error| code
+  // in case of failures in Write operation.
+  bool Write(const void* bytes, size_t count, ErrorCode *error) override;
+
+  // Wrapper around close. Returns 0 on success or -errno on error.
+  // Closes both 'path' given to Open() and the kernel path.
+  int Close() override;
+
+  // Returns |true| only if the manifest has been processed and it's valid.
+  bool IsManifestValid();
+
+  // Verifies the downloaded payload against the signed hash included in the
+  // payload, against the update check hash (which is in base64 format)  and
+  // size using the public key and returns ErrorCode::kSuccess on success, an
+  // error code on failure.  This method should be called after closing the
+  // stream. Note this method skips the signed hash check if the public key is
+  // unavailable; it returns ErrorCode::kSignedDeltaPayloadExpectedError if the
+  // public key is available but the delta payload doesn't include a signature.
+  ErrorCode VerifyPayload(const std::string& update_check_response_hash,
+                          const uint64_t update_check_response_size);
+
+  // Reads from the update manifest the expected sizes and hashes of the target
+  // kernel and rootfs partitions. These values can be used for applied update
+  // hash verification. This method must be called after the update manifest has
+  // been parsed (e.g., after closing the stream). Returns true on success, and
+  // false on failure (e.g., when the values are not present in the update
+  // manifest).
+  bool GetNewPartitionInfo(uint64_t* kernel_size,
+                           chromeos::Blob* kernel_hash,
+                           uint64_t* rootfs_size,
+                           chromeos::Blob* rootfs_hash);
+
+  // Converts an ordered collection of Extent objects which contain data of
+  // length full_length to a comma-separated string. For each Extent, the
+  // string will have the start offset and then the length in bytes.
+  // The length value of the last extent in the string may be short, since
+  // the full length of all extents in the string is capped to full_length.
+  // Also, an extent starting at kSparseHole, appears as -1 in the string.
+  // For example, if the Extents are {1, 1}, {4, 2}, {kSparseHole, 1},
+  // {0, 1}, block_size is 4096, and full_length is 5 * block_size - 13,
+  // the resulting string will be: "4096:4096,16384:8192,-1:4096,0:4083"
+  static bool ExtentsToBsdiffPositionsString(
+      const google::protobuf::RepeatedPtrField<Extent>& extents,
+      uint64_t block_size,
+      uint64_t full_length,
+      std::string* positions_string);
+
+  // Returns true if a previous update attempt can be continued based on the
+  // persistent preferences and the new update check response hash.
+  static bool CanResumeUpdate(PrefsInterface* prefs,
+                              std::string update_check_response_hash);
+
+  // Resets the persistent update progress state to indicate that an update
+  // can't be resumed. Performs a quick update-in-progress reset if |quick| is
+  // true, otherwise resets all progress-related update state. Returns true on
+  // success, false otherwise.
+  static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick);
+
+  // Attempts to parse the update metadata starting from the beginning of
+  // |payload|. On success, returns kMetadataParseSuccess. Returns
+  // kMetadataParseInsufficientData if more data is needed to parse the complete
+  // metadata. Returns kMetadataParseError if the metadata can't be parsed given
+  // the payload.
+  MetadataParseResult ParsePayloadMetadata(const chromeos::Blob& payload,
+                                           ErrorCode* error);
+
+  void set_public_key_path(const std::string& public_key_path) {
+    public_key_path_ = public_key_path;
+  }
+
+  // Returns the byte offset at which the payload version can be found.
+  static uint64_t GetVersionOffset();
+
+  // Returns the byte offset where the size of the manifest is stored in
+  // a payload. This offset precedes the actual start of the manifest
+  // that's returned by the GetManifestOffset method.
+  static uint64_t GetManifestSizeOffset();
+
+  // Returns the byte offset at which the manifest protobuf begins in a
+  // payload.
+  static uint64_t GetManifestOffset();
+
+  // Returns the size of the payload metadata, which includes the payload header
+  // and the manifest. Is the header was not yet parsed, returns zero.
+  uint64_t GetMetadataSize() const;
+
+  // If the manifest was successfully parsed, copies it to |*out_manifest_p|.
+  // Returns true on success.
+  bool GetManifest(DeltaArchiveManifest* out_manifest_p) const;
+
+  // Returns the delta minor version. If this value is defined in the manifest,
+  // it returns that value, otherwise it returns the default value.
+  uint32_t GetMinorVersion() const;
+
+ private:
+  friend class DeltaPerformerTest;
+  FRIEND_TEST(DeltaPerformerTest, IsIdempotentOperationTest);
+  FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse);
+
+  // Appends up to |*count_p| bytes from |*bytes_p| to |buffer_|, but only to
+  // the extent that the size of |buffer_| does not exceed |max|. Advances
+  // |*cbytes_p| and decreases |*count_p| by the actual number of bytes copied,
+  // and returns this number.
+  size_t CopyDataToBuffer(const char** bytes_p, size_t* count_p, size_t max);
+
+  // If |op_result| is false, emits an error message using |op_type_name| and
+  // sets |*error| accordingly. Otherwise does nothing. Returns |op_result|.
+  bool HandleOpResult(bool op_result, const char* op_type_name,
+                      ErrorCode* error);
+
+  // Logs the progress of downloading/applying an update.
+  void LogProgress(const char* message_prefix);
+
+  // Update overall progress metrics, log as necessary.
+  void UpdateOverallProgress(bool force_log, const char* message_prefix);
+
+  // Verifies that the expected source partition hashes (if present) match the
+  // hashes for the current partitions. Returns true if there are no expected
+  // hashes in the payload (e.g., if it's a new-style full update) or if the
+  // hashes match; returns false otherwise.
+  bool VerifySourcePartitions();
+
+  // Returns true if enough of the delta file has been passed via Write()
+  // to be able to perform a given install operation.
+  bool CanPerformInstallOperation(
+      const DeltaArchiveManifest_InstallOperation& operation);
+
+  // Checks the integrity of the payload manifest. Returns true upon success,
+  // false otherwise.
+  ErrorCode ValidateManifest();
+
+  // Validates that the hash of the blobs corresponding to the given |operation|
+  // matches what's specified in the manifest in the payload.
+  // Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
+  ErrorCode ValidateOperationHash(
+      const DeltaArchiveManifest_InstallOperation& operation);
+
+  // Interprets the given |protobuf| as a DeltaArchiveManifest protocol buffer
+  // of the given protobuf_length and verifies that the signed hash of the
+  // metadata matches what's specified in the install plan from Omaha.
+  // Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
+  // This method must be called before any part of the |protobuf| is parsed
+  // so that a man-in-the-middle attack on the SSL connection to the payload
+  // server doesn't exploit any vulnerability in the code that parses the
+  // protocol buffer.
+  ErrorCode ValidateMetadataSignature(const void* protobuf,
+                                      uint64_t protobuf_length);
+
+  // Returns true on success.
+  bool PerformInstallOperation(
+      const DeltaArchiveManifest_InstallOperation& operation);
+
+  // These perform a specific type of operation and return true on success.
+  bool PerformReplaceOperation(
+      const DeltaArchiveManifest_InstallOperation& operation,
+      bool is_kernel_partition);
+  bool PerformMoveOperation(
+      const DeltaArchiveManifest_InstallOperation& operation,
+      bool is_kernel_partition);
+  bool PerformBsdiffOperation(
+      const DeltaArchiveManifest_InstallOperation& operation,
+      bool is_kernel_partition);
+  bool PerformSourceCopyOperation(
+      const DeltaArchiveManifest_InstallOperation& operation,
+      bool is_kernel_partition);
+  bool PerformSourceBsdiffOperation(
+      const DeltaArchiveManifest_InstallOperation& operation,
+      bool is_kernel_partition);
+
+  // Returns true if the payload signature message has been extracted from
+  // |operation|, false otherwise.
+  bool ExtractSignatureMessage(
+      const DeltaArchiveManifest_InstallOperation& operation);
+
+  // Updates the hash calculator with the bytes in |buffer_|. Then discard the
+  // content, ensuring that memory is being deallocated. If |do_advance_offset|,
+  // advances the internal offset counter accordingly.
+  void DiscardBuffer(bool do_advance_offset);
+
+  // Checkpoints the update progress into persistent storage to allow this
+  // update attempt to be resumed after reboot.
+  bool CheckpointUpdateProgress();
+
+  // Primes the required update state. Returns true if the update state was
+  // successfully initialized to a saved resume state or if the update is a new
+  // update. Returns false otherwise.
+  bool PrimeUpdateState();
+
+  // Sends UMA statistics for the given error code.
+  void SendUmaStat(ErrorCode code);
+
+  // If the Omaha response contains a public RSA key and we're allowed
+  // to use it (e.g. if we're in developer mode), extract the key from
+  // the response and store it in a temporary file and return true. In
+  // the affirmative the path to the temporary file is stored in
+  // |out_tmp_key| and it is the responsibility of the caller to clean
+  // it up.
+  bool GetPublicKeyFromResponse(base::FilePath *out_tmp_key);
+
+  // Update Engine preference store.
+  PrefsInterface* prefs_;
+
+  // Global context of the system.
+  SystemState* system_state_;
+
+  // Install Plan based on Omaha Response.
+  InstallPlan* install_plan_;
+
+  // File descriptor of open device.
+  FileDescriptorPtr fd_;
+
+  // File descriptor of the kernel device.
+  FileDescriptorPtr kernel_fd_;
+
+  // File descriptor of the source device.
+  FileDescriptorPtr source_fd_;
+
+  // File descriptor of the source kernel device.
+  FileDescriptorPtr source_kernel_fd_;
+
+  std::string path_;  // Path that fd_ refers to.
+  std::string kernel_path_;  // Path that kernel_fd_ refers to.
+
+  DeltaArchiveManifest manifest_;
+  bool manifest_parsed_;
+  bool manifest_valid_;
+  uint64_t metadata_size_;
+
+  // Index of the next operation to perform in the manifest.
+  size_t next_operation_num_;
+
+  // A buffer used for accumulating downloaded data. Initially, it stores the
+  // payload metadata; once that's downloaded and parsed, it stores data for the
+  // next update operation.
+  chromeos::Blob buffer_;
+  // Offset of buffer_ in the binary blobs section of the update.
+  uint64_t buffer_offset_;
+
+  // Last |buffer_offset_| value updated as part of the progress update.
+  uint64_t last_updated_buffer_offset_;
+
+  // The block size (parsed from the manifest).
+  uint32_t block_size_;
+
+  // Calculates the payload hash.
+  OmahaHashCalculator hash_calculator_;
+
+  // Saves the signed hash context.
+  std::string signed_hash_context_;
+
+  // Signatures message blob extracted directly from the payload.
+  chromeos::Blob signatures_message_data_;
+
+  // The public key to be used. Provided as a member so that tests can
+  // override with test keys.
+  std::string public_key_path_;
+
+  // The number of bytes received so far, used for progress tracking.
+  size_t total_bytes_received_;
+
+  // The number rootfs and total operations in a payload, once we know them.
+  size_t num_rootfs_operations_;
+  size_t num_total_operations_;
+
+  // An overall progress counter, which should reflect both download progress
+  // and the ratio of applied operations. Range is 0-100.
+  unsigned overall_progress_;
+
+  // The last progress chunk recorded.
+  unsigned last_progress_chunk_;
+
+  // The timeout after which we should force emitting a progress log (constant),
+  // and the actual point in time for the next forced log to be emitted.
+  const base::TimeDelta forced_progress_log_wait_;
+  base::Time forced_progress_log_time_;
+
+  // The delta minor payload version supported by DeltaPerformer.
+  uint32_t supported_minor_version_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DELTA_PERFORMER_H_
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
new file mode 100644
index 0000000..5737422
--- /dev/null
+++ b/delta_performer_unittest.cc
@@ -0,0 +1,1416 @@
+// Copyright (c) 2012 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/delta_performer.h"
+
+#include <inttypes.h>
+#include <sys/mount.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_util.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using testing::Return;
+using testing::_;
+using test_utils::kRandomString;
+using test_utils::ScopedLoopMounter;
+using test_utils::System;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+extern const char* kUnittestPrivateKey2Path;
+extern const char* kUnittestPublicKey2Path;
+
+static const char* kBogusMetadataSignature1 =
+    "awSFIUdUZz2VWFiR+ku0Pj00V7bPQPQFYQSXjEXr3vaw3TE4xHV5CraY3/YrZpBv"
+    "J5z4dSBskoeuaO1TNC/S6E05t+yt36tE4Fh79tMnJ/z9fogBDXWgXLEUyG78IEQr"
+    "YH6/eBsQGT2RJtBgXIXbZ9W+5G9KmGDoPOoiaeNsDuqHiBc/58OFsrxskH8E6vMS"
+    "BmMGGk82mvgzic7ApcoURbCGey1b3Mwne/hPZ/bb9CIyky8Og9IfFMdL2uAweOIR"
+    "fjoTeLYZpt+WN65Vu7jJ0cQN8e1y+2yka5112wpRf/LLtPgiAjEZnsoYpLUd7CoV"
+    "pLRtClp97kN2+tXGNBQqkA==";
+
+static const int kDefaultKernelSize = 4096;  // Something small for a test
+static const uint8_t kNewData[] = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ',
+                                   'n', 'e', 'w', ' ', 'd', 'a', 't', 'a', '.'};
+
+namespace {
+struct DeltaState {
+  string a_img;
+  string b_img;
+  string result_img;
+  int image_size;
+
+  string delta_path;
+  uint64_t metadata_size;
+
+  string old_kernel;
+  chromeos::Blob old_kernel_data;
+
+  string new_kernel;
+  chromeos::Blob new_kernel_data;
+
+  string result_kernel;
+  chromeos::Blob result_kernel_data;
+
+  // The in-memory copy of delta file.
+  chromeos::Blob delta;
+
+  // The mock system state object with which we initialize the
+  // delta performer.
+  FakeSystemState fake_system_state;
+};
+
+enum SignatureTest {
+  kSignatureNone,  // No payload signing.
+  kSignatureGenerator,  // Sign the payload at generation time.
+  kSignatureGenerated,  // Sign the payload after it's generated.
+  kSignatureGeneratedPlaceholder,  // Insert placeholder signatures, then real.
+  kSignatureGeneratedPlaceholderMismatch,  // Insert a wrong sized placeholder.
+  kSignatureGeneratedShell,  // Sign the generated payload through shell cmds.
+  kSignatureGeneratedShellBadKey,  // Sign with a bad key through shell cmds.
+  kSignatureGeneratedShellRotateCl1,  // Rotate key, test client v1
+  kSignatureGeneratedShellRotateCl2,  // Rotate key, test client v2
+};
+
+// Different options that determine what we should fill into the
+// install_plan.metadata_signature to simulate the contents received in the
+// Omaha response.
+enum MetadataSignatureTest {
+  kEmptyMetadataSignature,
+  kInvalidMetadataSignature,
+  kValidMetadataSignature,
+};
+
+enum OperationHashTest {
+  kInvalidOperationData,
+  kValidOperationData,
+};
+
+}  // namespace
+
+class DeltaPerformerTest : public ::testing::Test {
+ public:
+  // Test helper placed where it can easily be friended from DeltaPerformer.
+  static void RunManifestValidation(const DeltaArchiveManifest& manifest,
+                                    bool full_payload,
+                                    ErrorCode expected) {
+    MockPrefs prefs;
+    InstallPlan install_plan;
+    FakeSystemState fake_system_state;
+    DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+
+    // The install plan is for Full or Delta.
+    install_plan.is_full_update = full_payload;
+
+    // The Manifest we are validating.
+    performer.manifest_.CopyFrom(manifest);
+
+    EXPECT_EQ(expected, performer.ValidateManifest());
+  }
+
+  static void SetSupportedVersion(DeltaPerformer* performer,
+                                  uint64_t minor_version) {
+    performer->supported_minor_version_ = minor_version;
+  }
+};
+
+static void CompareFilesByBlock(const string& a_file, const string& b_file) {
+  chromeos::Blob a_data, b_data;
+  EXPECT_TRUE(utils::ReadFile(a_file, &a_data)) << "file failed: " << a_file;
+  EXPECT_TRUE(utils::ReadFile(b_file, &b_data)) << "file failed: " << b_file;
+
+  EXPECT_EQ(a_data.size(), b_data.size());
+  EXPECT_EQ(0, a_data.size() % kBlockSize);
+  for (size_t i = 0; i < a_data.size(); i += kBlockSize) {
+    EXPECT_EQ(0, i % kBlockSize);
+    chromeos::Blob a_sub(&a_data[i], &a_data[i + kBlockSize]);
+    chromeos::Blob b_sub(&b_data[i], &b_data[i + kBlockSize]);
+    EXPECT_TRUE(a_sub == b_sub) << "Block " << (i/kBlockSize) << " differs";
+  }
+}
+
+static bool WriteSparseFile(const string& path, off_t size) {
+  int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
+  TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+  ScopedFdCloser fd_closer(&fd);
+  off_t rc = lseek(fd, size + 1, SEEK_SET);
+  TEST_AND_RETURN_FALSE_ERRNO(rc != static_cast<off_t>(-1));
+  int return_code = ftruncate(fd, size);
+  TEST_AND_RETURN_FALSE_ERRNO(return_code == 0);
+  return true;
+}
+
+static size_t GetSignatureSize(const string& private_key_path) {
+  const chromeos::Blob data(1, 'x');
+  chromeos::Blob hash;
+  EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(data, &hash));
+  chromeos::Blob signature;
+  EXPECT_TRUE(PayloadSigner::SignHash(hash,
+                                      private_key_path,
+                                      &signature));
+  return signature.size();
+}
+
+static bool InsertSignaturePlaceholder(int signature_size,
+                                       const string& payload_path,
+                                       uint64_t* out_metadata_size) {
+  vector<chromeos::Blob> signatures;
+  signatures.push_back(chromeos::Blob(signature_size, 0));
+
+  return PayloadSigner::AddSignatureToPayload(
+      payload_path,
+      signatures,
+      payload_path,
+      out_metadata_size);
+}
+
+static void SignGeneratedPayload(const string& payload_path,
+                                 uint64_t* out_metadata_size) {
+  int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+  chromeos::Blob hash;
+  ASSERT_TRUE(PayloadSigner::HashPayloadForSigning(
+      payload_path,
+      vector<int>(1, signature_size),
+      &hash));
+  chromeos::Blob signature;
+  ASSERT_TRUE(PayloadSigner::SignHash(hash,
+                                      kUnittestPrivateKeyPath,
+                                      &signature));
+  ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(
+      payload_path,
+      vector<chromeos::Blob>(1, signature),
+      payload_path,
+      out_metadata_size));
+  EXPECT_TRUE(PayloadVerifier::VerifySignedPayload(
+      payload_path,
+      kUnittestPublicKeyPath,
+      kSignatureMessageOriginalVersion));
+}
+
+static void SignGeneratedShellPayload(SignatureTest signature_test,
+                                      const string& payload_path) {
+  string private_key_path = kUnittestPrivateKeyPath;
+  if (signature_test == kSignatureGeneratedShellBadKey) {
+    ASSERT_TRUE(utils::MakeTempFile("key.XXXXXX",
+                                    &private_key_path,
+                                    nullptr));
+  } else {
+    ASSERT_TRUE(signature_test == kSignatureGeneratedShell ||
+                signature_test == kSignatureGeneratedShellRotateCl1 ||
+                signature_test == kSignatureGeneratedShellRotateCl2);
+  }
+  ScopedPathUnlinker key_unlinker(private_key_path);
+  key_unlinker.set_should_remove(signature_test ==
+                                 kSignatureGeneratedShellBadKey);
+  // Generates a new private key that will not match the public key.
+  if (signature_test == kSignatureGeneratedShellBadKey) {
+    LOG(INFO) << "Generating a mismatched private key.";
+    ASSERT_EQ(0, System(base::StringPrintf(
+        "openssl genrsa -out %s 2048", private_key_path.c_str())));
+  }
+  int signature_size = GetSignatureSize(private_key_path);
+  string hash_file;
+  ASSERT_TRUE(utils::MakeTempFile("hash.XXXXXX", &hash_file, nullptr));
+  ScopedPathUnlinker hash_unlinker(hash_file);
+  string signature_size_string;
+  if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+      signature_test == kSignatureGeneratedShellRotateCl2)
+    signature_size_string = base::StringPrintf("%d:%d",
+                                               signature_size, signature_size);
+  else
+    signature_size_string = base::StringPrintf("%d", signature_size);
+  ASSERT_EQ(0,
+            System(base::StringPrintf(
+                "./delta_generator -in_file=%s -signature_size=%s "
+                "-out_hash_file=%s",
+                payload_path.c_str(),
+                signature_size_string.c_str(),
+                hash_file.c_str())));
+
+  // Pad the hash
+  chromeos::Blob hash;
+  ASSERT_TRUE(utils::ReadFile(hash_file, &hash));
+  ASSERT_TRUE(PayloadVerifier::PadRSA2048SHA256Hash(&hash));
+  ASSERT_TRUE(test_utils::WriteFileVector(hash_file, hash));
+
+  string sig_file;
+  ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file, nullptr));
+  ScopedPathUnlinker sig_unlinker(sig_file);
+  ASSERT_EQ(0,
+            System(base::StringPrintf(
+                "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+                private_key_path.c_str(),
+                hash_file.c_str(),
+                sig_file.c_str())));
+  string sig_file2;
+  ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file2, nullptr));
+  ScopedPathUnlinker sig2_unlinker(sig_file2);
+  if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+      signature_test == kSignatureGeneratedShellRotateCl2) {
+    ASSERT_EQ(0,
+              System(base::StringPrintf(
+                  "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+                  kUnittestPrivateKey2Path,
+                  hash_file.c_str(),
+                  sig_file2.c_str())));
+    // Append second sig file to first path
+    sig_file += ":" + sig_file2;
+  }
+
+  ASSERT_EQ(0,
+            System(base::StringPrintf(
+                "./delta_generator -in_file=%s -signature_file=%s "
+                "-out_file=%s",
+                payload_path.c_str(),
+                sig_file.c_str(),
+                payload_path.c_str())));
+  int verify_result =
+      System(base::StringPrintf(
+          "./delta_generator -in_file=%s -public_key=%s -public_key_version=%d",
+          payload_path.c_str(),
+          signature_test == kSignatureGeneratedShellRotateCl2 ?
+          kUnittestPublicKey2Path : kUnittestPublicKeyPath,
+          signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1));
+  if (signature_test == kSignatureGeneratedShellBadKey) {
+    ASSERT_NE(0, verify_result);
+  } else {
+    ASSERT_EQ(0, verify_result);
+  }
+}
+
+static void GenerateDeltaFile(bool full_kernel,
+                              bool full_rootfs,
+                              bool noop,
+                              off_t chunk_size,
+                              SignatureTest signature_test,
+                              DeltaState *state,
+                              uint32_t minor_version) {
+  EXPECT_TRUE(utils::MakeTempFile("a_img.XXXXXX", &state->a_img, nullptr));
+  EXPECT_TRUE(utils::MakeTempFile("b_img.XXXXXX", &state->b_img, nullptr));
+
+  // result_img is used in minor version 2. Instead of applying the update
+  // in-place on A, we apply it to a new image, result_img.
+  EXPECT_TRUE(
+      utils::MakeTempFile("result_img.XXXXXX", &state->result_img, nullptr));
+  test_utils::CreateExtImageAtPath(state->a_img, nullptr);
+
+  state->image_size = static_cast<int>(utils::FileSize(state->a_img));
+
+  // Extend the "partitions" holding the file system a bit.
+  EXPECT_EQ(0, System(base::StringPrintf(
+      "dd if=/dev/zero of=%s seek=%d bs=1 count=1 status=none",
+      state->a_img.c_str(),
+      state->image_size + 1024 * 1024 - 1)));
+  EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->a_img));
+
+  // Create ImageInfo A & B
+  ImageInfo old_image_info;
+  ImageInfo new_image_info;
+
+  if (!full_rootfs) {
+    old_image_info.set_channel("src-channel");
+    old_image_info.set_board("src-board");
+    old_image_info.set_version("src-version");
+    old_image_info.set_key("src-key");
+    old_image_info.set_build_channel("src-build-channel");
+    old_image_info.set_build_version("src-build-version");
+  }
+
+  new_image_info.set_channel("test-channel");
+  new_image_info.set_board("test-board");
+  new_image_info.set_version("test-version");
+  new_image_info.set_key("test-key");
+  new_image_info.set_build_channel("test-build-channel");
+  new_image_info.set_build_version("test-build-version");
+
+  // Make some changes to the A image.
+  {
+    string a_mnt;
+    ScopedLoopMounter b_mounter(state->a_img, &a_mnt, 0);
+
+    chromeos::Blob hardtocompress;
+    while (hardtocompress.size() < 3 * kBlockSize) {
+      hardtocompress.insert(hardtocompress.end(),
+                            std::begin(kRandomString), std::end(kRandomString));
+    }
+    EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+                                                    a_mnt.c_str()).c_str(),
+                                 hardtocompress.data(),
+                                 hardtocompress.size()));
+
+    chromeos::Blob zeros(16 * 1024, 0);
+    EXPECT_EQ(zeros.size(),
+              base::WriteFile(base::FilePath(base::StringPrintf(
+                                  "%s/move-to-sparse", a_mnt.c_str())),
+                              reinterpret_cast<const char*>(zeros.data()),
+                              zeros.size()));
+
+    EXPECT_TRUE(
+        WriteSparseFile(base::StringPrintf("%s/move-from-sparse",
+                                           a_mnt.c_str()), 16 * 1024));
+
+    EXPECT_EQ(0,
+              System(base::StringPrintf("dd if=/dev/zero of=%s/move-semi-sparse"
+                                        " bs=1 seek=4096 count=1 status=none",
+                                        a_mnt.c_str()).c_str()));
+
+    // Write 1 MiB of 0xff to try to catch the case where writing a bsdiff
+    // patch fails to zero out the final block.
+    chromeos::Blob ones(1024 * 1024, 0xff);
+    EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/ones",
+                                                    a_mnt.c_str()).c_str(),
+                                 ones.data(),
+                                 ones.size()));
+  }
+
+  if (noop) {
+    EXPECT_TRUE(base::CopyFile(base::FilePath(state->a_img),
+                               base::FilePath(state->b_img)));
+    old_image_info = new_image_info;
+  } else {
+    if (minor_version == kSourceMinorPayloadVersion) {
+      // Create a result image with image_size bytes of garbage, followed by
+      // zeroes after the rootfs, like image A and B have.
+      chromeos::Blob ones(state->image_size, 0xff);
+      ones.insert(ones.end(), 1024 * 1024, 0);
+      EXPECT_TRUE(utils::WriteFile(state->result_img.c_str(),
+                                   ones.data(),
+                                   ones.size()));
+      EXPECT_EQ(utils::FileSize(state->a_img),
+                utils::FileSize(state->result_img));
+    }
+
+    test_utils::CreateExtImageAtPath(state->b_img, nullptr);
+    EXPECT_EQ(0, System(base::StringPrintf(
+        "dd if=/dev/zero of=%s seek=%d bs=1 count=1 status=none",
+        state->b_img.c_str(),
+        state->image_size + 1024 * 1024 - 1)));
+    EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->b_img));
+
+    // Make some changes to the B image.
+    string b_mnt;
+    ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0);
+
+    EXPECT_EQ(0, System(base::StringPrintf("cp %s/hello %s/hello2",
+                                           b_mnt.c_str(),
+                                           b_mnt.c_str()).c_str()));
+    EXPECT_EQ(0, System(base::StringPrintf("rm %s/hello",
+                                           b_mnt.c_str()).c_str()));
+    EXPECT_EQ(0, System(base::StringPrintf("mv %s/hello2 %s/hello",
+                                           b_mnt.c_str(),
+                                           b_mnt.c_str()).c_str()));
+    EXPECT_EQ(0, System(base::StringPrintf("echo foo > %s/foo",
+                                           b_mnt.c_str()).c_str()));
+    EXPECT_EQ(0, System(base::StringPrintf("touch %s/emptyfile",
+                                           b_mnt.c_str()).c_str()));
+    EXPECT_TRUE(WriteSparseFile(base::StringPrintf("%s/fullsparse",
+                                                   b_mnt.c_str()),
+                                                   1024 * 1024));
+
+    EXPECT_TRUE(
+        WriteSparseFile(base::StringPrintf("%s/move-to-sparse", b_mnt.c_str()),
+                        16 * 1024));
+
+    chromeos::Blob zeros(16 * 1024, 0);
+    EXPECT_EQ(zeros.size(),
+              base::WriteFile(base::FilePath(base::StringPrintf(
+                                  "%s/move-from-sparse", b_mnt.c_str())),
+                              reinterpret_cast<const char*>(zeros.data()),
+                              zeros.size()));
+
+    EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+                                           "of=%s/move-semi-sparse "
+                                           "bs=1 seek=4096 count=1 status=none",
+                                           b_mnt.c_str()).c_str()));
+
+    EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+                                           "of=%s/partsparse bs=1 "
+                                           "seek=4096 count=1 status=none",
+                                           b_mnt.c_str()).c_str()));
+    EXPECT_EQ(0, System(base::StringPrintf("cp %s/srchardlink0 %s/tmp && "
+                                           "mv %s/tmp %s/srchardlink1",
+                                           b_mnt.c_str(),
+                                           b_mnt.c_str(),
+                                           b_mnt.c_str(),
+                                           b_mnt.c_str()).c_str()));
+    EXPECT_EQ(0, System(
+        base::StringPrintf("rm %s/boguslink && echo foobar > %s/boguslink",
+                           b_mnt.c_str(), b_mnt.c_str()).c_str()));
+
+    chromeos::Blob hardtocompress;
+    while (hardtocompress.size() < 3 * kBlockSize) {
+      hardtocompress.insert(hardtocompress.end(),
+                            std::begin(kRandomString), std::end(kRandomString));
+    }
+    EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+                                              b_mnt.c_str()).c_str(),
+                                 hardtocompress.data(),
+                                 hardtocompress.size()));
+  }
+
+  string old_kernel;
+  EXPECT_TRUE(utils::MakeTempFile("old_kernel.XXXXXX",
+                                  &state->old_kernel,
+                                  nullptr));
+
+  string new_kernel;
+  EXPECT_TRUE(utils::MakeTempFile("new_kernel.XXXXXX",
+                                  &state->new_kernel,
+                                  nullptr));
+
+  string result_kernel;
+  EXPECT_TRUE(utils::MakeTempFile("result_kernel.XXXXXX",
+                                  &state->result_kernel,
+                                  nullptr));
+
+  state->old_kernel_data.resize(kDefaultKernelSize);
+  state->new_kernel_data.resize(state->old_kernel_data.size());
+  state->result_kernel_data.resize(state->old_kernel_data.size());
+  test_utils::FillWithData(&state->old_kernel_data);
+  test_utils::FillWithData(&state->new_kernel_data);
+  test_utils::FillWithData(&state->result_kernel_data);
+
+  // change the new kernel data
+  std::copy(std::begin(kNewData), std::end(kNewData),
+            state->new_kernel_data.begin());
+
+  if (noop) {
+    state->old_kernel_data = state->new_kernel_data;
+  }
+
+  // Write kernels to disk
+  EXPECT_TRUE(utils::WriteFile(state->old_kernel.c_str(),
+                               state->old_kernel_data.data(),
+                               state->old_kernel_data.size()));
+  EXPECT_TRUE(utils::WriteFile(state->new_kernel.c_str(),
+                               state->new_kernel_data.data(),
+                               state->new_kernel_data.size()));
+  EXPECT_TRUE(utils::WriteFile(state->result_kernel.c_str(),
+                               state->result_kernel_data.data(),
+                               state->result_kernel_data.size()));
+
+  EXPECT_TRUE(utils::MakeTempFile("delta.XXXXXX",
+                                  &state->delta_path,
+                                  nullptr));
+  LOG(INFO) << "delta path: " << state->delta_path;
+  {
+    const string private_key =
+        signature_test == kSignatureGenerator ? kUnittestPrivateKeyPath : "";
+
+    PayloadGenerationConfig payload_config;
+    payload_config.is_delta = !full_rootfs;
+    payload_config.chunk_size = chunk_size;
+    payload_config.rootfs_partition_size = kRootFSPartitionSize;
+    payload_config.minor_version = minor_version;
+    if (!full_rootfs) {
+      payload_config.source.rootfs.path = state->a_img;
+      if (!full_kernel)
+        payload_config.source.kernel.path = state->old_kernel;
+      payload_config.source.image_info = old_image_info;
+      EXPECT_TRUE(payload_config.source.LoadImageSize());
+      EXPECT_TRUE(payload_config.source.rootfs.OpenFilesystem());
+      EXPECT_TRUE(payload_config.source.kernel.OpenFilesystem());
+    } else {
+      if (payload_config.chunk_size == -1)
+        payload_config.chunk_size = kDefaultChunkSize;
+    }
+    payload_config.target.rootfs.path = state->b_img;
+    payload_config.target.kernel.path = state->new_kernel;
+    payload_config.target.image_info = new_image_info;
+    EXPECT_TRUE(payload_config.target.LoadImageSize());
+    EXPECT_TRUE(payload_config.target.rootfs.OpenFilesystem());
+    EXPECT_TRUE(payload_config.target.kernel.OpenFilesystem());
+
+    EXPECT_TRUE(payload_config.Validate());
+    EXPECT_TRUE(
+        GenerateUpdatePayloadFile(
+            payload_config,
+            state->delta_path,
+            private_key,
+            &state->metadata_size));
+  }
+
+  if (signature_test == kSignatureGeneratedPlaceholder ||
+      signature_test == kSignatureGeneratedPlaceholderMismatch) {
+    int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+    LOG(INFO) << "Inserting placeholder signature.";
+    ASSERT_TRUE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+                                           &state->metadata_size));
+
+    if (signature_test == kSignatureGeneratedPlaceholderMismatch) {
+      signature_size -= 1;
+      LOG(INFO) << "Inserting mismatched placeholder signature.";
+      ASSERT_FALSE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+                                              &state->metadata_size));
+      return;
+    }
+  }
+
+  if (signature_test == kSignatureGenerated ||
+      signature_test == kSignatureGeneratedPlaceholder ||
+      signature_test == kSignatureGeneratedPlaceholderMismatch) {
+    // Generate the signed payload and update the metadata size in state to
+    // reflect the new size after adding the signature operation to the
+    // manifest.
+    LOG(INFO) << "Signing payload.";
+    SignGeneratedPayload(state->delta_path, &state->metadata_size);
+  } else if (signature_test == kSignatureGeneratedShell ||
+             signature_test == kSignatureGeneratedShellBadKey ||
+             signature_test == kSignatureGeneratedShellRotateCl1 ||
+             signature_test == kSignatureGeneratedShellRotateCl2) {
+    SignGeneratedShellPayload(signature_test, state->delta_path);
+  }
+}
+
+static void ApplyDeltaFile(bool full_kernel, bool full_rootfs, bool noop,
+                           SignatureTest signature_test, DeltaState* state,
+                           bool hash_checks_mandatory,
+                           OperationHashTest op_hash_test,
+                           DeltaPerformer** performer,
+                           uint32_t minor_version) {
+  // Check the metadata.
+  {
+    DeltaArchiveManifest manifest;
+    EXPECT_TRUE(PayloadVerifier::LoadPayload(state->delta_path,
+                                             &state->delta,
+                                             &manifest,
+                                             &state->metadata_size));
+    LOG(INFO) << "Metadata size: " << state->metadata_size;
+
+
+
+    if (signature_test == kSignatureNone) {
+      EXPECT_FALSE(manifest.has_signatures_offset());
+      EXPECT_FALSE(manifest.has_signatures_size());
+    } else {
+      EXPECT_TRUE(manifest.has_signatures_offset());
+      EXPECT_TRUE(manifest.has_signatures_size());
+      Signatures sigs_message;
+      EXPECT_TRUE(sigs_message.ParseFromArray(
+          &state->delta[state->metadata_size + manifest.signatures_offset()],
+          manifest.signatures_size()));
+      if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+          signature_test == kSignatureGeneratedShellRotateCl2)
+        EXPECT_EQ(2, sigs_message.signatures_size());
+      else
+        EXPECT_EQ(1, sigs_message.signatures_size());
+      const Signatures_Signature& signature = sigs_message.signatures(0);
+      EXPECT_EQ(1, signature.version());
+
+      uint64_t expected_sig_data_length = 0;
+      vector<string> key_paths{kUnittestPrivateKeyPath};
+      if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+          signature_test == kSignatureGeneratedShellRotateCl2) {
+        key_paths.push_back(kUnittestPrivateKey2Path);
+      }
+      EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+          key_paths,
+          &expected_sig_data_length));
+      EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
+      EXPECT_FALSE(signature.data().empty());
+    }
+
+    if (noop) {
+      EXPECT_EQ(0, manifest.install_operations_size());
+      EXPECT_EQ(1, manifest.kernel_install_operations_size());
+    }
+
+    if (full_kernel) {
+      EXPECT_FALSE(manifest.has_old_kernel_info());
+    } else {
+      EXPECT_EQ(state->old_kernel_data.size(),
+                manifest.old_kernel_info().size());
+      EXPECT_FALSE(manifest.old_kernel_info().hash().empty());
+    }
+
+    EXPECT_EQ(manifest.new_image_info().channel(), "test-channel");
+    EXPECT_EQ(manifest.new_image_info().board(), "test-board");
+    EXPECT_EQ(manifest.new_image_info().version(), "test-version");
+    EXPECT_EQ(manifest.new_image_info().key(), "test-key");
+    EXPECT_EQ(manifest.new_image_info().build_channel(), "test-build-channel");
+    EXPECT_EQ(manifest.new_image_info().build_version(), "test-build-version");
+
+    if (!full_rootfs) {
+      if (noop) {
+        EXPECT_EQ(manifest.old_image_info().channel(), "test-channel");
+        EXPECT_EQ(manifest.old_image_info().board(), "test-board");
+        EXPECT_EQ(manifest.old_image_info().version(), "test-version");
+        EXPECT_EQ(manifest.old_image_info().key(), "test-key");
+        EXPECT_EQ(manifest.old_image_info().build_channel(),
+                  "test-build-channel");
+        EXPECT_EQ(manifest.old_image_info().build_version(),
+                  "test-build-version");
+      } else {
+        EXPECT_EQ(manifest.old_image_info().channel(), "src-channel");
+        EXPECT_EQ(manifest.old_image_info().board(), "src-board");
+        EXPECT_EQ(manifest.old_image_info().version(), "src-version");
+        EXPECT_EQ(manifest.old_image_info().key(), "src-key");
+        EXPECT_EQ(manifest.old_image_info().build_channel(),
+                  "src-build-channel");
+        EXPECT_EQ(manifest.old_image_info().build_version(),
+                  "src-build-version");
+      }
+    }
+
+
+    if (full_rootfs) {
+      EXPECT_FALSE(manifest.has_old_rootfs_info());
+      EXPECT_FALSE(manifest.has_old_image_info());
+      EXPECT_TRUE(manifest.has_new_image_info());
+    } else {
+      EXPECT_EQ(state->image_size, manifest.old_rootfs_info().size());
+      EXPECT_FALSE(manifest.old_rootfs_info().hash().empty());
+    }
+
+    EXPECT_EQ(state->new_kernel_data.size(), manifest.new_kernel_info().size());
+    EXPECT_EQ(state->image_size, manifest.new_rootfs_info().size());
+
+    EXPECT_FALSE(manifest.new_kernel_info().hash().empty());
+    EXPECT_FALSE(manifest.new_rootfs_info().hash().empty());
+  }
+
+  MockPrefs prefs;
+  EXPECT_CALL(prefs, SetInt64(kPrefsManifestMetadataSize,
+                              state->metadata_size)).WillOnce(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _))
+      .WillOnce(Return(false));
+  EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataOffset, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataLength, _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSHA256Context, _))
+      .WillRepeatedly(Return(true));
+  if (op_hash_test == kValidOperationData && signature_test != kSignatureNone) {
+    EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignedSHA256Context, _))
+        .WillOnce(Return(true));
+    EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignatureBlob, _))
+        .WillOnce(Return(true));
+  }
+
+  // Update the A image in place.
+  InstallPlan install_plan;
+  install_plan.hash_checks_mandatory = hash_checks_mandatory;
+  install_plan.metadata_size = state->metadata_size;
+  install_plan.is_full_update = full_kernel && full_rootfs;
+  install_plan.source_path = state->a_img.c_str();
+  install_plan.kernel_source_path = state->old_kernel.c_str();
+
+  LOG(INFO) << "Setting payload metadata size in Omaha  = "
+            << state->metadata_size;
+  ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+      state->delta.data(),
+      state->metadata_size,
+      kUnittestPrivateKeyPath,
+      &install_plan.metadata_signature));
+  EXPECT_FALSE(install_plan.metadata_signature.empty());
+
+  *performer = new DeltaPerformer(&prefs,
+                                  &state->fake_system_state,
+                                  &install_plan);
+  EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+  (*performer)->set_public_key_path(kUnittestPublicKeyPath);
+  DeltaPerformerTest::SetSupportedVersion(*performer, minor_version);
+
+  EXPECT_EQ(state->image_size,
+            OmahaHashCalculator::RawHashOfFile(
+                state->a_img,
+                state->image_size,
+                &install_plan.source_rootfs_hash));
+  EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(
+                  state->old_kernel_data,
+                  &install_plan.source_kernel_hash));
+
+  // With minor version 2, we want the target to be the new image, result_img,
+  // but with version 1, we want to update A in place.
+  if (minor_version == kSourceMinorPayloadVersion) {
+    EXPECT_EQ(0, (*performer)->Open(state->result_img.c_str(), 0, 0));
+    EXPECT_TRUE((*performer)->OpenKernel(state->result_kernel.c_str()));
+  } else {
+    EXPECT_EQ(0, (*performer)->Open(state->a_img.c_str(), 0, 0));
+    EXPECT_TRUE((*performer)->OpenKernel(state->old_kernel.c_str()));
+  }
+
+
+  ErrorCode expected_error, actual_error;
+  bool continue_writing;
+  switch (op_hash_test) {
+    case kInvalidOperationData: {
+      // Muck with some random offset post the metadata size so that
+      // some operation hash will result in a mismatch.
+      int some_offset = state->metadata_size + 300;
+      LOG(INFO) << "Tampered value at offset: " << some_offset;
+      state->delta[some_offset]++;
+      expected_error = ErrorCode::kDownloadOperationHashMismatch;
+      continue_writing = false;
+      break;
+    }
+
+    case kValidOperationData:
+    default:
+      // no change.
+      expected_error = ErrorCode::kSuccess;
+      continue_writing = true;
+      break;
+  }
+
+  // Write at some number of bytes per operation. Arbitrarily chose 5.
+  const size_t kBytesPerWrite = 5;
+  for (size_t i = 0; i < state->delta.size(); i += kBytesPerWrite) {
+    size_t count = std::min(state->delta.size() - i, kBytesPerWrite);
+    bool write_succeeded = ((*performer)->Write(&state->delta[i],
+                                                count,
+                                                &actual_error));
+    // Normally write_succeeded should be true every time and
+    // actual_error should be ErrorCode::kSuccess. If so, continue the loop.
+    // But if we seeded an operation hash error above, then write_succeeded
+    // will be false. The failure may happen at any operation n. So, all
+    // Writes until n-1 should succeed and the nth operation will fail with
+    // actual_error. In this case, we should bail out of the loop because
+    // we cannot proceed applying the delta.
+    if (!write_succeeded) {
+      LOG(INFO) << "Write failed. Checking if it failed with expected error";
+      EXPECT_EQ(expected_error, actual_error);
+      if (!continue_writing) {
+        LOG(INFO) << "Cannot continue writing. Bailing out.";
+        break;
+      }
+    }
+
+    EXPECT_EQ(ErrorCode::kSuccess, actual_error);
+  }
+
+  // If we had continued all the way through, Close should succeed.
+  // Otherwise, it should fail. Check appropriately.
+  bool close_result = (*performer)->Close();
+  if (continue_writing)
+    EXPECT_EQ(0, close_result);
+  else
+    EXPECT_LE(0, close_result);
+}
+
+void VerifyPayloadResult(DeltaPerformer* performer,
+                         DeltaState* state,
+                         ErrorCode expected_result,
+                         uint32_t minor_version) {
+  if (!performer) {
+    EXPECT_TRUE(!"Skipping payload verification since performer is null.");
+    return;
+  }
+
+  int expected_times = (expected_result == ErrorCode::kSuccess) ? 1 : 0;
+  EXPECT_CALL(*(state->fake_system_state.mock_payload_state()),
+              DownloadComplete()).Times(expected_times);
+
+  LOG(INFO) << "Verifying payload for expected result "
+            << expected_result;
+  EXPECT_EQ(expected_result, performer->VerifyPayload(
+      OmahaHashCalculator::OmahaHashOfData(state->delta),
+      state->delta.size()));
+  LOG(INFO) << "Verified payload.";
+
+  if (expected_result != ErrorCode::kSuccess) {
+    // no need to verify new partition if VerifyPayload failed.
+    return;
+  }
+
+  chromeos::Blob updated_kernel_partition;
+  if (minor_version == kSourceMinorPayloadVersion) {
+    CompareFilesByBlock(state->result_kernel, state->new_kernel);
+    CompareFilesByBlock(state->result_img, state->b_img);
+    EXPECT_TRUE(utils::ReadFile(state->result_kernel,
+                                &updated_kernel_partition));
+  } else {
+    CompareFilesByBlock(state->old_kernel, state->new_kernel);
+    CompareFilesByBlock(state->a_img, state->b_img);
+    EXPECT_TRUE(utils::ReadFile(state->old_kernel, &updated_kernel_partition));
+  }
+
+  ASSERT_GE(updated_kernel_partition.size(), arraysize(kNewData));
+  EXPECT_TRUE(std::equal(std::begin(kNewData), std::end(kNewData),
+                         updated_kernel_partition.begin()));
+
+  uint64_t new_kernel_size;
+  chromeos::Blob new_kernel_hash;
+  uint64_t new_rootfs_size;
+  chromeos::Blob new_rootfs_hash;
+  EXPECT_TRUE(performer->GetNewPartitionInfo(&new_kernel_size,
+                                             &new_kernel_hash,
+                                             &new_rootfs_size,
+                                             &new_rootfs_hash));
+  EXPECT_EQ(kDefaultKernelSize, new_kernel_size);
+  chromeos::Blob expected_new_kernel_hash;
+  EXPECT_TRUE(OmahaHashCalculator::RawHashOfData(state->new_kernel_data,
+                                                 &expected_new_kernel_hash));
+  EXPECT_TRUE(expected_new_kernel_hash == new_kernel_hash);
+  EXPECT_EQ(state->image_size, new_rootfs_size);
+  chromeos::Blob expected_new_rootfs_hash;
+  EXPECT_EQ(state->image_size,
+            OmahaHashCalculator::RawHashOfFile(state->b_img,
+                                               state->image_size,
+                                               &expected_new_rootfs_hash));
+  EXPECT_TRUE(expected_new_rootfs_hash == new_rootfs_hash);
+}
+
+void VerifyPayload(DeltaPerformer* performer,
+                   DeltaState* state,
+                   SignatureTest signature_test,
+                   uint32_t minor_version) {
+  ErrorCode expected_result = ErrorCode::kSuccess;
+  switch (signature_test) {
+    case kSignatureNone:
+      expected_result = ErrorCode::kSignedDeltaPayloadExpectedError;
+      break;
+    case kSignatureGeneratedShellBadKey:
+      expected_result = ErrorCode::kDownloadPayloadPubKeyVerificationError;
+      break;
+    default: break;  // appease gcc
+  }
+
+  VerifyPayloadResult(performer, state, expected_result, minor_version);
+}
+
+void DoSmallImageTest(bool full_kernel, bool full_rootfs, bool noop,
+                      off_t chunk_size,
+                      SignatureTest signature_test,
+                      bool hash_checks_mandatory, uint32_t minor_version) {
+  DeltaState state;
+  DeltaPerformer *performer = nullptr;
+  GenerateDeltaFile(full_kernel, full_rootfs, noop, chunk_size,
+                    signature_test, &state, minor_version);
+
+  ScopedPathUnlinker a_img_unlinker(state.a_img);
+  ScopedPathUnlinker b_img_unlinker(state.b_img);
+  ScopedPathUnlinker new_img_unlinker(state.result_img);
+  ScopedPathUnlinker delta_unlinker(state.delta_path);
+  ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+  ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+  ScopedPathUnlinker result_kernel_unlinker(state.result_kernel);
+  ApplyDeltaFile(full_kernel, full_rootfs, noop, signature_test,
+                 &state, hash_checks_mandatory, kValidOperationData,
+                 &performer, minor_version);
+  VerifyPayload(performer, &state, signature_test, minor_version);
+  delete performer;
+}
+
+// Calls delta performer's Write method by pretending to pass in bytes from a
+// delta file whose metadata size is actual_metadata_size and tests if all
+// checks are correctly performed if the install plan contains
+// expected_metadata_size and that the result of the parsing are as per
+// hash_checks_mandatory flag.
+void DoMetadataSizeTest(uint64_t expected_metadata_size,
+                        uint64_t actual_metadata_size,
+                        bool hash_checks_mandatory) {
+  MockPrefs prefs;
+  InstallPlan install_plan;
+  install_plan.hash_checks_mandatory = hash_checks_mandatory;
+  FakeSystemState fake_system_state;
+  DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+  EXPECT_EQ(0, performer.Open("/dev/null", 0, 0));
+  EXPECT_TRUE(performer.OpenKernel("/dev/null"));
+
+  // Set a valid magic string and version number 1.
+  EXPECT_TRUE(performer.Write("CrAU", 4));
+  uint64_t version = htobe64(1);
+  EXPECT_TRUE(performer.Write(&version, 8));
+
+  install_plan.metadata_size = expected_metadata_size;
+  ErrorCode error_code;
+  // When filling in size in manifest, exclude the size of the 20-byte header.
+  uint64_t size_in_manifest = htobe64(actual_metadata_size - 20);
+  bool result = performer.Write(&size_in_manifest, 8, &error_code);
+  if (expected_metadata_size == actual_metadata_size ||
+      !hash_checks_mandatory) {
+    EXPECT_TRUE(result);
+  } else {
+    EXPECT_FALSE(result);
+    EXPECT_EQ(ErrorCode::kDownloadInvalidMetadataSize, error_code);
+  }
+
+  EXPECT_LT(performer.Close(), 0);
+}
+
+// Generates a valid delta file but tests the delta performer by suppling
+// different metadata signatures as per omaha_metadata_signature flag and
+// sees if the result of the parsing are as per hash_checks_mandatory flag.
+void DoMetadataSignatureTest(MetadataSignatureTest metadata_signature_test,
+                             SignatureTest signature_test,
+                             bool hash_checks_mandatory) {
+  DeltaState state;
+
+  // Using kSignatureNone since it doesn't affect the results of our test.
+  // If we've to use other signature options, then we'd have to get the
+  // metadata size again after adding the signing operation to the manifest.
+  GenerateDeltaFile(true, true, false, -1, signature_test, &state,
+                    DeltaPerformer::kFullPayloadMinorVersion);
+
+  ScopedPathUnlinker a_img_unlinker(state.a_img);
+  ScopedPathUnlinker b_img_unlinker(state.b_img);
+  ScopedPathUnlinker delta_unlinker(state.delta_path);
+  ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+  ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+
+  // Loads the payload and parses the manifest.
+  chromeos::Blob payload;
+  EXPECT_TRUE(utils::ReadFile(state.delta_path, &payload));
+  LOG(INFO) << "Payload size: " << payload.size();
+
+  InstallPlan install_plan;
+  install_plan.hash_checks_mandatory = hash_checks_mandatory;
+  install_plan.metadata_size = state.metadata_size;
+
+  DeltaPerformer::MetadataParseResult expected_result, actual_result;
+  ErrorCode expected_error, actual_error;
+
+  // Fill up the metadata signature in install plan according to the test.
+  switch (metadata_signature_test) {
+    case kEmptyMetadataSignature:
+      install_plan.metadata_signature.clear();
+      expected_result = DeltaPerformer::kMetadataParseError;
+      expected_error = ErrorCode::kDownloadMetadataSignatureMissingError;
+      break;
+
+    case kInvalidMetadataSignature:
+      install_plan.metadata_signature = kBogusMetadataSignature1;
+      expected_result = DeltaPerformer::kMetadataParseError;
+      expected_error = ErrorCode::kDownloadMetadataSignatureMismatch;
+      break;
+
+    case kValidMetadataSignature:
+    default:
+      // Set the install plan's metadata size to be the same as the one
+      // in the manifest so that we pass the metadata size checks. Only
+      // then we can get to manifest signature checks.
+      ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+          payload.data(),
+          state.metadata_size,
+          kUnittestPrivateKeyPath,
+          &install_plan.metadata_signature));
+      EXPECT_FALSE(install_plan.metadata_signature.empty());
+      expected_result = DeltaPerformer::kMetadataParseSuccess;
+      expected_error = ErrorCode::kSuccess;
+      break;
+  }
+
+  // Ignore the expected result/error if hash checks are not mandatory.
+  if (!hash_checks_mandatory) {
+    expected_result = DeltaPerformer::kMetadataParseSuccess;
+    expected_error = ErrorCode::kSuccess;
+  }
+
+  // Create the delta performer object.
+  MockPrefs prefs;
+  DeltaPerformer delta_performer(&prefs,
+                                 &state.fake_system_state,
+                                 &install_plan);
+
+  // Use the public key corresponding to the private key used above to
+  // sign the metadata.
+  EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+  delta_performer.set_public_key_path(kUnittestPublicKeyPath);
+
+  // Init actual_error with an invalid value so that we make sure
+  // ParsePayloadMetadata properly populates it in all cases.
+  actual_error = ErrorCode::kUmaReportedMax;
+  actual_result = delta_performer.ParsePayloadMetadata(payload, &actual_error);
+
+  EXPECT_EQ(expected_result, actual_result);
+  EXPECT_EQ(expected_error, actual_error);
+
+  // Check that the parsed metadata size is what's expected. This test
+  // implicitly confirms that the metadata signature is valid, if required.
+  EXPECT_EQ(state.metadata_size, delta_performer.GetMetadataSize());
+}
+
+void DoOperationHashMismatchTest(OperationHashTest op_hash_test,
+                                 bool hash_checks_mandatory) {
+  DeltaState state;
+  uint64_t minor_version = DeltaPerformer::kFullPayloadMinorVersion;
+  GenerateDeltaFile(true, true, false, -1, kSignatureGenerated, &state,
+                    minor_version);
+  ScopedPathUnlinker a_img_unlinker(state.a_img);
+  ScopedPathUnlinker b_img_unlinker(state.b_img);
+  ScopedPathUnlinker delta_unlinker(state.delta_path);
+  ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+  ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+  DeltaPerformer *performer = nullptr;
+  ApplyDeltaFile(true, true, false, kSignatureGenerated, &state,
+                 hash_checks_mandatory, op_hash_test, &performer,
+                 minor_version);
+  delete performer;
+}
+
+TEST(DeltaPerformerTest, ExtentsToByteStringTest) {
+  uint64_t test[] = {1, 1, 4, 2, 0, 1};
+  COMPILE_ASSERT(arraysize(test) % 2 == 0, array_size_uneven);
+  const uint64_t block_size = 4096;
+  const uint64_t file_length = 4 * block_size - 13;
+
+  google::protobuf::RepeatedPtrField<Extent> extents;
+  for (size_t i = 0; i < arraysize(test); i += 2) {
+    Extent* extent = extents.Add();
+    extent->set_start_block(test[i]);
+    extent->set_num_blocks(test[i + 1]);
+  }
+
+  string expected_output = "4096:4096,16384:8192,0:4083";
+  string actual_output;
+  EXPECT_TRUE(DeltaPerformer::ExtentsToBsdiffPositionsString(extents,
+                                                             block_size,
+                                                             file_length,
+                                                             &actual_output));
+  EXPECT_EQ(expected_output, actual_output);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullGoodTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(DeltaPerformer::kFullPayloadMinorVersion);
+
+  DeltaPerformerTest::RunManifestValidation(manifest, true,
+                                            ErrorCode::kSuccess);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestDeltaGoodTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_old_kernel_info();
+  manifest.mutable_old_rootfs_info();
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+  DeltaPerformerTest::RunManifestValidation(manifest, false,
+                                            ErrorCode::kSuccess);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullUnsetMinorVersion) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  DeltaPerformerTest::RunManifestValidation(manifest, true,
+                                            ErrorCode::kSuccess);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  DeltaPerformerTest::RunManifestValidation(
+      manifest, false,
+      ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullOldKernelTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_old_kernel_info();
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+  DeltaPerformerTest::RunManifestValidation(
+      manifest, true,
+      ErrorCode::kPayloadMismatchedType);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestFullOldRootfsTest) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+  manifest.mutable_old_rootfs_info();
+  manifest.mutable_new_kernel_info();
+  manifest.mutable_new_rootfs_info();
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+  DeltaPerformerTest::RunManifestValidation(
+      manifest, true,
+      ErrorCode::kPayloadMismatchedType);
+}
+
+TEST(DeltaPerformerTest, ValidateManifestBadMinorVersion) {
+  // The Manifest we are validating.
+  DeltaArchiveManifest manifest;
+
+  // Generate a bad version number.
+  manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion +
+                             10000);
+
+  DeltaPerformerTest::RunManifestValidation(
+      manifest, false,
+      ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignaturePlaceholderTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedPlaceholder,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignaturePlaceholderMismatchTest) {
+  DeltaState state;
+  GenerateDeltaFile(false, false, false, -1,
+                    kSignatureGeneratedPlaceholderMismatch, &state,
+                    kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageChunksTest) {
+  DoSmallImageTest(false, false, false, kBlockSize, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootFullKernelSmallImageTest) {
+  DoSmallImageTest(true, false, false, -1, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootFullSmallImageTest) {
+  DoSmallImageTest(true, true, false, -1, kSignatureGenerator,
+                   true, DeltaPerformer::kFullPayloadMinorVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNoopSmallImageTest) {
+  DoSmallImageTest(false, false, true, -1, kSignatureGenerator,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignNoneTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureNone,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGenerated,
+                   true, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShell,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellBadKeyTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellBadKey,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellRotateCl1Test) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl1,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSignGeneratedShellRotateCl2Test) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl2,
+                   false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, RunAsRootSmallImageSourceOpsTest) {
+  DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+                   false, kSourceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerTest, BadDeltaMagicTest) {
+  MockPrefs prefs;
+  InstallPlan install_plan;
+  FakeSystemState fake_system_state;
+  DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+  EXPECT_EQ(0, performer.Open("/dev/null", 0, 0));
+  EXPECT_TRUE(performer.OpenKernel("/dev/null"));
+  EXPECT_TRUE(performer.Write("junk", 4));
+  EXPECT_TRUE(performer.Write("morejunk", 8));
+  EXPECT_FALSE(performer.Write("morejunk", 8));
+  EXPECT_LT(performer.Close(), 0);
+}
+
+TEST(DeltaPerformerTest, WriteUpdatesPayloadState) {
+  MockPrefs prefs;
+  InstallPlan install_plan;
+  FakeSystemState fake_system_state;
+  DeltaPerformer performer(&prefs, &fake_system_state, &install_plan);
+  EXPECT_EQ(0, performer.Open("/dev/null", 0, 0));
+  EXPECT_TRUE(performer.OpenKernel("/dev/null"));
+
+  EXPECT_CALL(*(fake_system_state.mock_payload_state()),
+              DownloadProgress(4)).Times(1);
+  EXPECT_CALL(*(fake_system_state.mock_payload_state()),
+              DownloadProgress(8)).Times(2);
+
+  EXPECT_TRUE(performer.Write("junk", 4));
+  EXPECT_TRUE(performer.Write("morejunk", 8));
+  EXPECT_FALSE(performer.Write("morejunk", 8));
+  EXPECT_LT(performer.Close(), 0);
+}
+
+TEST(DeltaPerformerTest, MissingMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(0, 75456, true);
+}
+
+TEST(DeltaPerformerTest, MissingNonMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(0, 123456, false);
+}
+
+TEST(DeltaPerformerTest, InvalidMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(13000, 140000, true);
+}
+
+TEST(DeltaPerformerTest, InvalidNonMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(40000, 50000, false);
+}
+
+TEST(DeltaPerformerTest, ValidMandatoryMetadataSizeTest) {
+  DoMetadataSizeTest(85376, 85376, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryEmptyMetadataSignatureTest) {
+  DoMetadataSignatureTest(kEmptyMetadataSignature, kSignatureGenerated, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNonMandatoryEmptyMetadataSignatureTest) {
+  DoMetadataSignatureTest(kEmptyMetadataSignature, kSignatureGenerated, false);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryInvalidMetadataSignatureTest) {
+  DoMetadataSignatureTest(kInvalidMetadataSignature, kSignatureGenerated, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNonMandatoryInvalidMetadataSignatureTest) {
+  DoMetadataSignatureTest(kInvalidMetadataSignature, kSignatureGenerated,
+                          false);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryValidMetadataSignature1Test) {
+  DoMetadataSignatureTest(kValidMetadataSignature, kSignatureNone, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryValidMetadataSignature2Test) {
+  DoMetadataSignatureTest(kValidMetadataSignature, kSignatureGenerated, true);
+}
+
+TEST(DeltaPerformerTest, RunAsRootNonMandatoryValidMetadataSignatureTest) {
+  DoMetadataSignatureTest(kValidMetadataSignature, kSignatureGenerated, false);
+}
+
+TEST(DeltaPerformerTest, RunAsRootMandatoryOperationHashMismatchTest) {
+  DoOperationHashMismatchTest(kInvalidOperationData, true);
+}
+
+TEST(DeltaPerformerTest, UsePublicKeyFromResponse) {
+  MockPrefs prefs;
+  FakeSystemState fake_system_state;
+  InstallPlan install_plan;
+  base::FilePath key_path;
+
+  // The result of the GetPublicKeyResponse() method is based on three things
+  //
+  //  1. Whether it's an official build; and
+  //  2. Whether the Public RSA key to be used is in the root filesystem; and
+  //  3. Whether the response has a public key
+  //
+  // We test all eight combinations to ensure that we only use the
+  // public key in the response if
+  //
+  //  a. it's not an official build; and
+  //  b. there is no key in the root filesystem.
+
+  DeltaPerformer *performer = new DeltaPerformer(&prefs,
+                                                 &fake_system_state,
+                                                 &install_plan);
+  FakeHardware* fake_hardware = fake_system_state.fake_hardware();
+
+  string temp_dir;
+  EXPECT_TRUE(utils::MakeTempDirectory("PublicKeyFromResponseTests.XXXXXX",
+                                       &temp_dir));
+  string non_existing_file = temp_dir + "/non-existing";
+  string existing_file = temp_dir + "/existing";
+  EXPECT_EQ(0, System(base::StringPrintf("touch %s", existing_file.c_str())));
+
+  // Non-official build, non-existing public-key, key in response -> true
+  fake_hardware->SetIsOfficialBuild(false);
+  performer->public_key_path_ = non_existing_file;
+  install_plan.public_key_rsa = "VGVzdAo=";  // result of 'echo "Test" | base64'
+  EXPECT_TRUE(performer->GetPublicKeyFromResponse(&key_path));
+  EXPECT_FALSE(key_path.empty());
+  EXPECT_EQ(unlink(key_path.value().c_str()), 0);
+  // Same with official build -> false
+  fake_hardware->SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, existing public-key, key in response -> false
+  fake_hardware->SetIsOfficialBuild(false);
+  performer->public_key_path_ = existing_file;
+  install_plan.public_key_rsa = "VGVzdAo=";  // result of 'echo "Test" | base64'
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+  // Same with official build -> false
+  fake_hardware->SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, non-existing public-key, no key in response -> false
+  fake_hardware->SetIsOfficialBuild(false);
+  performer->public_key_path_ = non_existing_file;
+  install_plan.public_key_rsa = "";
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+  // Same with official build -> false
+  fake_hardware->SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, existing public-key, no key in response -> false
+  fake_hardware->SetIsOfficialBuild(false);
+  performer->public_key_path_ = existing_file;
+  install_plan.public_key_rsa = "";
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+  // Same with official build -> false
+  fake_hardware->SetIsOfficialBuild(true);
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+  // Non-official build, non-existing public-key, key in response
+  // but invalid base64 -> false
+  fake_hardware->SetIsOfficialBuild(false);
+  performer->public_key_path_ = non_existing_file;
+  install_plan.public_key_rsa = "not-valid-base64";
+  EXPECT_FALSE(performer->GetPublicKeyFromResponse(&key_path));
+
+  delete performer;
+  EXPECT_TRUE(test_utils::RecursiveUnlinkDir(temp_dir));
+}
+
+TEST(DeltaPerformerTest, MinorVersionsMatch) {
+  // Test that the minor version in update_engine.conf that is installed to
+  // the image matches the supported delta minor version in the update engine.
+  uint32_t minor_version;
+  chromeos::KeyValueStore store;
+  EXPECT_TRUE(store.Load(base::FilePath("update_engine.conf")));
+  EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+  EXPECT_EQ(DeltaPerformer::kSupportedMinorPayloadVersion, minor_version);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/download_action.cc b/download_action.cc
new file mode 100644
index 0000000..8e6061c
--- /dev/null
+++ b/download_action.cc
@@ -0,0 +1,323 @@
+// Copyright (c) 2011 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/download_action.h"
+
+#include <errno.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/utils.h"
+
+using base::FilePath;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+DownloadAction::DownloadAction(PrefsInterface* prefs,
+                               SystemState* system_state,
+                               HttpFetcher* http_fetcher)
+    : prefs_(prefs),
+      system_state_(system_state),
+      http_fetcher_(http_fetcher),
+      writer_(nullptr),
+      code_(ErrorCode::kSuccess),
+      delegate_(nullptr),
+      bytes_received_(0),
+      p2p_sharing_fd_(-1),
+      p2p_visible_(true) {}
+
+DownloadAction::~DownloadAction() {}
+
+void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) {
+  if (p2p_sharing_fd_ != -1) {
+    if (close(p2p_sharing_fd_) != 0) {
+      PLOG(ERROR) << "Error closing p2p sharing fd";
+    }
+    p2p_sharing_fd_ = -1;
+  }
+
+  if (delete_p2p_file) {
+    FilePath path =
+      system_state_->p2p_manager()->FileGetPath(p2p_file_id_);
+    if (unlink(path.value().c_str()) != 0) {
+      PLOG(ERROR) << "Error deleting p2p file " << path.value();
+    } else {
+      LOG(INFO) << "Deleted p2p file " << path.value();
+    }
+  }
+
+  // Don't use p2p from this point onwards.
+  p2p_file_id_.clear();
+}
+
+bool DownloadAction::SetupP2PSharingFd() {
+  P2PManager *p2p_manager = system_state_->p2p_manager();
+
+  if (!p2p_manager->FileShare(p2p_file_id_, install_plan_.payload_size)) {
+    LOG(ERROR) << "Unable to share file via p2p";
+    CloseP2PSharingFd(true);  // delete p2p file
+    return false;
+  }
+
+  // File has already been created (and allocated, xattrs been
+  // populated etc.) by FileShare() so just open it for writing.
+  FilePath path = p2p_manager->FileGetPath(p2p_file_id_);
+  p2p_sharing_fd_ = open(path.value().c_str(), O_WRONLY);
+  if (p2p_sharing_fd_ == -1) {
+    PLOG(ERROR) << "Error opening file " << path.value();
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return false;
+  }
+
+  // Ensure file to share is world-readable, otherwise
+  // p2p-server and p2p-http-server can't access it.
+  //
+  // (Q: Why doesn't the file have mode 0644 already? A: Because
+  // the process-wide umask is set to 0700 in main.cc.)
+  if (fchmod(p2p_sharing_fd_, 0644) != 0) {
+    PLOG(ERROR) << "Error setting mode 0644 on " << path.value();
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return false;
+  }
+
+  // All good.
+  LOG(INFO) << "Writing payload contents to " << path.value();
+  p2p_manager->FileGetVisible(p2p_file_id_, &p2p_visible_);
+  return true;
+}
+
+void DownloadAction::WriteToP2PFile(const void* data,
+                                    size_t length,
+                                    off_t file_offset) {
+  if (p2p_sharing_fd_ == -1) {
+    if (!SetupP2PSharingFd())
+      return;
+  }
+
+  // Check that the file is at least |file_offset| bytes long - if
+  // it's not something is wrong and we must immediately delete the
+  // file to avoid propagating this problem to other peers.
+  //
+  // How can this happen? It could be that we're resuming an update
+  // after a system crash... in this case, it could be that
+  //
+  //  1. the p2p file didn't get properly synced to stable storage; or
+  //  2. the file was deleted at bootup (it's in /var/cache after all); or
+  //  3. other reasons
+  off_t p2p_size = utils::FileSize(p2p_sharing_fd_);
+  if (p2p_size < 0) {
+    PLOG(ERROR) << "Error getting file status for p2p file";
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return;
+  }
+  if (p2p_size < file_offset) {
+    LOG(ERROR) << "Wanting to write to file offset " << file_offset
+               << " but existing p2p file is only " << p2p_size
+               << " bytes.";
+    CloseP2PSharingFd(true);  // Delete p2p file.
+    return;
+  }
+
+  off_t cur_file_offset = lseek(p2p_sharing_fd_, file_offset, SEEK_SET);
+  if (cur_file_offset != static_cast<off_t>(file_offset)) {
+    PLOG(ERROR) << "Error seeking to position "
+                << file_offset << " in p2p file";
+    CloseP2PSharingFd(true);  // Delete p2p file.
+  } else {
+    // OK, seeking worked, now write the data
+    ssize_t bytes_written = write(p2p_sharing_fd_, data, length);
+    if (bytes_written != static_cast<ssize_t>(length)) {
+      PLOG(ERROR) << "Error writing "
+                  << length << " bytes at file offset "
+                  << file_offset << " in p2p file";
+      CloseP2PSharingFd(true);  // Delete p2p file.
+    }
+  }
+}
+
+void DownloadAction::PerformAction() {
+  http_fetcher_->set_delegate(this);
+
+  // Get the InstallPlan and read it
+  CHECK(HasInputObject());
+  install_plan_ = GetInputObject();
+  bytes_received_ = 0;
+
+  install_plan_.Dump();
+
+  if (writer_) {
+    LOG(INFO) << "Using writer for test.";
+  } else {
+    delta_performer_.reset(new DeltaPerformer(prefs_,
+                                              system_state_,
+                                              &install_plan_));
+    writer_ = delta_performer_.get();
+  }
+  int rc = writer_->Open(install_plan_.install_path.c_str(),
+                         O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE,
+                         0644);
+  if (rc < 0) {
+    LOG(ERROR) << "Unable to open output file " << install_plan_.install_path;
+    // report error to processor
+    processor_->ActionComplete(this, ErrorCode::kInstallDeviceOpenError);
+    return;
+  }
+  if (delta_performer_.get() &&
+      !delta_performer_->OpenKernel(
+          install_plan_.kernel_install_path.c_str())) {
+    LOG(ERROR) << "Unable to open kernel file "
+               << install_plan_.kernel_install_path.c_str();
+    writer_->Close();
+    processor_->ActionComplete(this, ErrorCode::kKernelDeviceOpenError);
+    return;
+  }
+  if (delegate_) {
+    delegate_->SetDownloadStatus(true);  // Set to active.
+  }
+
+  if (system_state_ != nullptr) {
+    const PayloadStateInterface* payload_state = system_state_->payload_state();
+    string file_id = utils::CalculateP2PFileId(install_plan_.payload_hash,
+                                               install_plan_.payload_size);
+    if (payload_state->GetUsingP2PForSharing()) {
+      // If we're sharing the update, store the file_id to convey
+      // that we should write to the file.
+      p2p_file_id_ = file_id;
+      LOG(INFO) << "p2p file id: " << p2p_file_id_;
+    } else {
+      // Even if we're not sharing the update, it could be that
+      // there's a partial file from a previous attempt with the same
+      // hash. If this is the case, we NEED to clean it up otherwise
+      // we're essentially timing out other peers downloading from us
+      // (since we're never going to complete the file).
+      FilePath path = system_state_->p2p_manager()->FileGetPath(file_id);
+      if (!path.empty()) {
+        if (unlink(path.value().c_str()) != 0) {
+          PLOG(ERROR) << "Error deleting p2p file " << path.value();
+        } else {
+          LOG(INFO) << "Deleting partial p2p file " << path.value()
+                    << " since we're not using p2p to share.";
+        }
+      }
+    }
+
+    // Tweak timeouts on the HTTP fetcher if we're downloading from a
+    // local peer.
+    if (payload_state->GetUsingP2PForDownloading() &&
+        payload_state->GetP2PUrl() == install_plan_.download_url) {
+      LOG(INFO) << "Tweaking HTTP fetcher since we're downloading via p2p";
+      http_fetcher_->set_low_speed_limit(kDownloadP2PLowSpeedLimitBps,
+                                         kDownloadP2PLowSpeedTimeSeconds);
+      http_fetcher_->set_max_retry_count(kDownloadP2PMaxRetryCount);
+      http_fetcher_->set_connect_timeout(kDownloadP2PConnectTimeoutSeconds);
+    }
+  }
+
+  http_fetcher_->BeginTransfer(install_plan_.download_url);
+}
+
+void DownloadAction::TerminateProcessing() {
+  if (writer_) {
+    writer_->Close();
+    writer_ = nullptr;
+  }
+  if (delegate_) {
+    delegate_->SetDownloadStatus(false);  // Set to inactive.
+  }
+  CloseP2PSharingFd(false);  // Keep p2p file.
+  // Terminates the transfer. The action is terminated, if necessary, when the
+  // TransferTerminated callback is received.
+  http_fetcher_->TerminateTransfer();
+}
+
+void DownloadAction::SeekToOffset(off_t offset) {
+  bytes_received_ = offset;
+}
+
+void DownloadAction::ReceivedBytes(HttpFetcher* fetcher,
+                                   const void* bytes,
+                                   size_t length) {
+  // Note that bytes_received_ is the current offset.
+  if (!p2p_file_id_.empty()) {
+    WriteToP2PFile(bytes, length, bytes_received_);
+  }
+
+  bytes_received_ += length;
+  if (delegate_)
+    delegate_->BytesReceived(bytes_received_, install_plan_.payload_size);
+  if (writer_ && !writer_->Write(bytes, length, &code_)) {
+    LOG(ERROR) << "Error " << code_ << " in DeltaPerformer's Write method when "
+               << "processing the received payload -- Terminating processing";
+    // Delete p2p file, if applicable.
+    if (!p2p_file_id_.empty())
+      CloseP2PSharingFd(true);
+    // Don't tell the action processor that the action is complete until we get
+    // the TransferTerminated callback. Otherwise, this and the HTTP fetcher
+    // objects may get destroyed before all callbacks are complete.
+    TerminateProcessing();
+    return;
+  }
+
+  // Call p2p_manager_->FileMakeVisible() when we've successfully
+  // verified the manifest!
+  if (!p2p_visible_ &&
+      delta_performer_.get() && delta_performer_->IsManifestValid()) {
+    LOG(INFO) << "Manifest has been validated. Making p2p file visible.";
+    system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_);
+    p2p_visible_ = true;
+  }
+}
+
+void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
+  if (writer_) {
+    LOG_IF(WARNING, writer_->Close() != 0) << "Error closing the writer.";
+    writer_ = nullptr;
+  }
+  if (delegate_) {
+    delegate_->SetDownloadStatus(false);  // Set to inactive.
+  }
+  ErrorCode code =
+      successful ? ErrorCode::kSuccess : ErrorCode::kDownloadTransferError;
+  if (code == ErrorCode::kSuccess && delta_performer_.get()) {
+    code = delta_performer_->VerifyPayload(install_plan_.payload_hash,
+                                           install_plan_.payload_size);
+    if (code != ErrorCode::kSuccess) {
+      LOG(ERROR) << "Download of " << install_plan_.download_url
+                 << " failed due to payload verification error.";
+      // Delete p2p file, if applicable.
+      if (!p2p_file_id_.empty())
+        CloseP2PSharingFd(true);
+    } else if (!delta_performer_->GetNewPartitionInfo(
+        &install_plan_.kernel_size,
+        &install_plan_.kernel_hash,
+        &install_plan_.rootfs_size,
+        &install_plan_.rootfs_hash)) {
+      LOG(ERROR) << "Unable to get new partition hash info.";
+      code = ErrorCode::kDownloadNewPartitionInfoError;
+    }
+  }
+
+  // Write the path to the output pipe if we're successful.
+  if (code == ErrorCode::kSuccess && HasOutputPipe())
+    SetOutputObject(install_plan_);
+  processor_->ActionComplete(this, code);
+}
+
+void DownloadAction::TransferTerminated(HttpFetcher *fetcher) {
+  if (code_ != ErrorCode::kSuccess) {
+    processor_->ActionComplete(this, code_);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/download_action.h b/download_action.h
new file mode 100644
index 0000000..0d51d9a
--- /dev/null
+++ b/download_action.h
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 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_DOWNLOAD_ACTION_H_
+#define UPDATE_ENGINE_DOWNLOAD_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+
+#include <curl/curl.h>
+
+#include "update_engine/action.h"
+#include "update_engine/delta_performer.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/system_state.h"
+
+// The Download Action downloads a specified url to disk. The url should point
+// to an update in a delta payload format. The payload will be piped into a
+// DeltaPerformer that will apply the delta to the disk.
+
+namespace chromeos_update_engine {
+
+class DownloadActionDelegate {
+ public:
+  virtual ~DownloadActionDelegate() = default;
+
+  // Called right before starting the download with |active| set to
+  // true. Called after completing the download with |active| set to
+  // false.
+  virtual void SetDownloadStatus(bool active) = 0;
+
+  // Called periodically after bytes are received. This method will be
+  // invoked only if the download is active. |bytes_received| is the
+  // number of bytes downloaded thus far. |total| is the number of
+  // bytes expected.
+  virtual void BytesReceived(uint64_t bytes_received, uint64_t total) = 0;
+};
+
+class PrefsInterface;
+
+class DownloadAction : public InstallPlanAction,
+                       public HttpFetcherDelegate {
+ public:
+  // Takes ownership of the passed in HttpFetcher. Useful for testing.
+  // A good calling pattern is:
+  // DownloadAction(prefs, system_state, new WhateverHttpFetcher);
+  DownloadAction(PrefsInterface* prefs,
+                 SystemState* system_state,
+                 HttpFetcher* http_fetcher);
+  ~DownloadAction() override;
+  void PerformAction() override;
+  void TerminateProcessing() override;
+
+  // Testing
+  void SetTestFileWriter(FileWriter* writer) {
+    writer_ = writer;
+  }
+
+  int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+  // Debugging/logging
+  static std::string StaticType() { return "DownloadAction"; }
+  std::string Type() const override { return StaticType(); }
+
+  // HttpFetcherDelegate methods (see http_fetcher.h)
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override;
+  void SeekToOffset(off_t offset) override;
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+  void TransferTerminated(HttpFetcher* fetcher) override;
+
+  DownloadActionDelegate* delegate() const { return delegate_; }
+  void set_delegate(DownloadActionDelegate* delegate) {
+    delegate_ = delegate;
+  }
+
+  HttpFetcher* http_fetcher() { return http_fetcher_.get(); }
+
+  // Returns the p2p file id for the file being written or the empty
+  // string if we're not writing to a p2p file.
+  std::string p2p_file_id() { return p2p_file_id_; }
+
+ private:
+  // Closes the file descriptor for the p2p file being written and
+  // clears |p2p_file_id_| to indicate that we're no longer sharing
+  // the file. If |delete_p2p_file| is True, also deletes the file.
+  // If there is no p2p file descriptor, this method does nothing.
+  void CloseP2PSharingFd(bool delete_p2p_file);
+
+  // Starts sharing the p2p file. Must be called before
+  // WriteToP2PFile(). Returns True if this worked.
+  bool SetupP2PSharingFd();
+
+  // Writes |length| bytes of payload from |data| into |file_offset|
+  // of the p2p file. Also does sanity checks; for example ensures we
+  // don't end up with a file with holes in it.
+  //
+  // This method does nothing if SetupP2PSharingFd() hasn't been
+  // called or if CloseP2PSharingFd() has been called.
+  void WriteToP2PFile(const void* data, size_t length, off_t file_offset);
+
+  // The InstallPlan passed in
+  InstallPlan install_plan_;
+
+  // Update Engine preference store.
+  PrefsInterface* prefs_;
+
+  // Global context for the system.
+  SystemState* system_state_;
+
+  // Pointer to the HttpFetcher that does the http work.
+  std::unique_ptr<HttpFetcher> http_fetcher_;
+
+  // The FileWriter that downloaded data should be written to. It will
+  // either point to *decompressing_file_writer_ or *delta_performer_.
+  FileWriter* writer_;
+
+  std::unique_ptr<DeltaPerformer> delta_performer_;
+
+  // Used by TransferTerminated to figure if this action terminated itself or
+  // was terminated by the action processor.
+  ErrorCode code_;
+
+  // For reporting status to outsiders
+  DownloadActionDelegate* delegate_;
+  uint64_t bytes_received_;
+
+  // The file-id for the file we're sharing or the empty string
+  // if we're not using p2p to share.
+  std::string p2p_file_id_;
+
+  // The file descriptor for the p2p file used for caching the payload or -1
+  // if we're not using p2p to share.
+  int p2p_sharing_fd_;
+
+  // Set to |false| if p2p file is not visible.
+  bool p2p_visible_;
+
+  DISALLOW_COPY_AND_ASSIGN(DownloadAction);
+};
+
+// We want to be sure that we're compiled with large file support on linux,
+// just in case we find ourselves downloading large images.
+COMPILE_ASSERT(8 == sizeof(off_t), off_t_not_64_bit);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DOWNLOAD_ACTION_H_
diff --git a/download_action_unittest.cc b/download_action_unittest.cc
new file mode 100644
index 0000000..254dbd7
--- /dev/null
+++ b/download_action_unittest.cc
@@ -0,0 +1,641 @@
+// Copyright (c) 2011 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/download_action.h"
+
+#include <glib.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/location.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using base::FilePath;
+using base::ReadFileToString;
+using base::WriteFile;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Return;
+using testing::_;
+using test_utils::ScopedTempFile;
+
+class DownloadActionTest : public ::testing::Test { };
+
+namespace {
+class DownloadActionDelegateMock : public DownloadActionDelegate {
+ public:
+  MOCK_METHOD1(SetDownloadStatus, void(bool active));
+  MOCK_METHOD2(BytesReceived, void(uint64_t bytes_received, uint64_t total));
+};
+
+class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  explicit DownloadActionTestProcessorDelegate(ErrorCode expected_code)
+      : processing_done_called_(false),
+        expected_code_(expected_code) {}
+  ~DownloadActionTestProcessorDelegate() override {
+    EXPECT_TRUE(processing_done_called_);
+  }
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) override {
+    chromeos::MessageLoop::current()->BreakLoop();
+    chromeos::Blob found_data;
+    ASSERT_TRUE(utils::ReadFile(path_, &found_data));
+    if (expected_code_ != ErrorCode::kDownloadWriteError) {
+      ASSERT_EQ(expected_data_.size(), found_data.size());
+      for (unsigned i = 0; i < expected_data_.size(); i++) {
+        EXPECT_EQ(expected_data_[i], found_data[i]);
+      }
+    }
+    processing_done_called_ = true;
+  }
+
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) override {
+    const string type = action->Type();
+    if (type == DownloadAction::StaticType()) {
+      EXPECT_EQ(expected_code_, code);
+    } else {
+      EXPECT_EQ(ErrorCode::kSuccess, code);
+    }
+  }
+
+  string path_;
+  chromeos::Blob expected_data_;
+  bool processing_done_called_;
+  ErrorCode expected_code_;
+};
+
+class TestDirectFileWriter : public DirectFileWriter {
+ public:
+  TestDirectFileWriter() : fail_write_(0), current_write_(0) {}
+  void set_fail_write(int fail_write) { fail_write_ = fail_write; }
+
+  virtual bool Write(const void* bytes, size_t count) {
+    if (++current_write_ == fail_write_) {
+      return false;
+    }
+    return DirectFileWriter::Write(bytes, count);
+  }
+
+ private:
+  // If positive, fail on the |fail_write_| call to Write.
+  int fail_write_;
+  int current_write_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+                             MockHttpFetcher* http_fetcher) {
+  processor->StartProcessing();
+  http_fetcher->SetOffset(1);
+}
+
+void TestWithData(const chromeos::Blob& data,
+                  int fail_write,
+                  bool use_download_delegate) {
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  // TODO(adlr): see if we need a different file for build bots
+  ScopedTempFile output_temp_file;
+  TestDirectFileWriter writer;
+  writer.set_fail_write(fail_write);
+
+  // We pull off the first byte from data and seek past it.
+
+  string hash =
+      OmahaHashCalculator::OmahaHashOfBytes(&data[1], data.size() - 1);
+  uint64_t size = data.size();
+  InstallPlan install_plan(false,
+                           false,
+                           "",
+                           size,
+                           hash,
+                           0,
+                           "",
+                           output_temp_file.GetPath(),
+                           "",
+                           "",
+                           "",
+                           "");
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  MockPrefs prefs;
+  MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(),
+                                                      data.size(),
+                                                      nullptr);
+  // takes ownership of passed in HttpFetcher
+  DownloadAction download_action(&prefs, nullptr, http_fetcher);
+  download_action.SetTestFileWriter(&writer);
+  BondActions(&feeder_action, &download_action);
+  DownloadActionDelegateMock download_delegate;
+  if (use_download_delegate) {
+    InSequence s;
+    download_action.set_delegate(&download_delegate);
+    EXPECT_CALL(download_delegate, SetDownloadStatus(true)).Times(1);
+    if (data.size() > kMockHttpFetcherChunkSize)
+      EXPECT_CALL(download_delegate,
+                  BytesReceived(1 + kMockHttpFetcherChunkSize, _));
+    EXPECT_CALL(download_delegate, BytesReceived(_, _)).Times(AtLeast(1));
+    EXPECT_CALL(download_delegate, SetDownloadStatus(false)).Times(1);
+  }
+  ErrorCode expected_code = ErrorCode::kSuccess;
+  if (fail_write > 0)
+    expected_code = ErrorCode::kDownloadWriteError;
+  DownloadActionTestProcessorDelegate delegate(expected_code);
+  delegate.expected_data_ = chromeos::Blob(data.begin() + 1, data.end());
+  delegate.path_ = output_temp_file.GetPath();
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&download_action);
+
+  loop.PostTask(FROM_HERE,
+                base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+}
+}  // namespace
+
+TEST(DownloadActionTest, SimpleTest) {
+  chromeos::Blob small;
+  const char* foo = "foo";
+  small.insert(small.end(), foo, foo + strlen(foo));
+  TestWithData(small,
+               0,  // fail_write
+               true);  // use_download_delegate
+}
+
+TEST(DownloadActionTest, LargeTest) {
+  chromeos::Blob big(5 * kMockHttpFetcherChunkSize);
+  char c = '0';
+  for (unsigned int i = 0; i < big.size(); i++) {
+    big[i] = c;
+    c = ('9' == c) ? '0' : c + 1;
+  }
+  TestWithData(big,
+               0,  // fail_write
+               true);  // use_download_delegate
+}
+
+TEST(DownloadActionTest, FailWriteTest) {
+  chromeos::Blob big(5 * kMockHttpFetcherChunkSize);
+  char c = '0';
+  for (unsigned int i = 0; i < big.size(); i++) {
+    big[i] = c;
+    c = ('9' == c) ? '0' : c + 1;
+  }
+  TestWithData(big,
+               2,  // fail_write
+               true);  // use_download_delegate
+}
+
+TEST(DownloadActionTest, NoDownloadDelegateTest) {
+  chromeos::Blob small;
+  const char* foo = "foofoo";
+  small.insert(small.end(), foo, foo + strlen(foo));
+  TestWithData(small,
+               0,  // fail_write
+               false);  // use_download_delegate
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  void ProcessingStopped(const ActionProcessor* processor) {
+    chromeos::MessageLoop::current()->BreakLoop();
+  }
+};
+
+void TerminateEarlyTestStarter(ActionProcessor* processor) {
+  processor->StartProcessing();
+  CHECK(processor->IsRunning());
+  processor->StopProcessing();
+}
+
+void TestTerminateEarly(bool use_download_delegate) {
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  chromeos::Blob data(kMockHttpFetcherChunkSize +
+                      kMockHttpFetcherChunkSize / 2);
+  memset(data.data(), 0, data.size());
+
+  ScopedTempFile temp_file;
+  {
+    DirectFileWriter writer;
+
+    // takes ownership of passed in HttpFetcher
+    ObjectFeederAction<InstallPlan> feeder_action;
+    InstallPlan install_plan(false, false, "", 0, "", 0, "",
+                             temp_file.GetPath(), "", "", "", "");
+    feeder_action.set_obj(install_plan);
+    MockPrefs prefs;
+    DownloadAction download_action(&prefs, nullptr,
+                                   new MockHttpFetcher(data.data(),
+                                                       data.size(),
+                                                       nullptr));
+    download_action.SetTestFileWriter(&writer);
+    DownloadActionDelegateMock download_delegate;
+    if (use_download_delegate) {
+      InSequence s;
+      download_action.set_delegate(&download_delegate);
+      EXPECT_CALL(download_delegate, SetDownloadStatus(true)).Times(1);
+      EXPECT_CALL(download_delegate, SetDownloadStatus(false)).Times(1);
+    }
+    TerminateEarlyTestProcessorDelegate delegate;
+    ActionProcessor processor;
+    processor.set_delegate(&delegate);
+    processor.EnqueueAction(&feeder_action);
+    processor.EnqueueAction(&download_action);
+    BondActions(&feeder_action, &download_action);
+
+    loop.PostTask(FROM_HERE,
+                  base::Bind(&TerminateEarlyTestStarter, &processor));
+    loop.Run();
+    EXPECT_FALSE(loop.PendingTasks());
+  }
+
+  // 1 or 0 chunks should have come through
+  const off_t resulting_file_size(utils::FileSize(temp_file.GetPath()));
+  EXPECT_GE(resulting_file_size, 0);
+  if (resulting_file_size != 0)
+    EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
+}
+
+}  // namespace
+
+TEST(DownloadActionTest, TerminateEarlyTest) {
+  TestTerminateEarly(true);
+}
+
+TEST(DownloadActionTest, TerminateEarlyNoDownloadDelegateTest) {
+  TestTerminateEarly(false);
+}
+
+class DownloadActionTestAction;
+
+template<>
+class ActionTraits<DownloadActionTestAction> {
+ public:
+  typedef InstallPlan OutputObjectType;
+  typedef InstallPlan InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class DownloadActionTestAction : public Action<DownloadActionTestAction> {
+ public:
+  DownloadActionTestAction() : did_run_(false) {}
+  typedef InstallPlan InputObjectType;
+  typedef InstallPlan OutputObjectType;
+  ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); }
+  ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); }
+  ActionProcessor* processor() { return processor_; }
+  void PerformAction() {
+    did_run_ = true;
+    ASSERT_TRUE(HasInputObject());
+    EXPECT_TRUE(expected_input_object_ == GetInputObject());
+    ASSERT_TRUE(processor());
+    processor()->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  string Type() const { return "DownloadActionTestAction"; }
+  InstallPlan expected_input_object_;
+  bool did_run_;
+};
+
+namespace {
+// This class is an ActionProcessorDelegate that simply terminates the
+// run loop when the ActionProcessor has completed processing. It's used
+// only by the test PassObjectOutTest.
+class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+    chromeos::MessageLoop::current()->BreakLoop();
+  }
+};
+
+}  // namespace
+
+TEST(DownloadActionTest, PassObjectOutTest) {
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  DirectFileWriter writer;
+
+  // takes ownership of passed in HttpFetcher
+  InstallPlan install_plan(false,
+                           false,
+                           "",
+                           1,
+                           OmahaHashCalculator::OmahaHashOfString("x"),
+                           0,
+                           "",
+                           "/dev/null",
+                           "/dev/null",
+                           "/dev/null",
+                           "/dev/null",
+                           "");
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  MockPrefs prefs;
+  DownloadAction download_action(&prefs, nullptr,
+                                 new MockHttpFetcher("x", 1, nullptr));
+  download_action.SetTestFileWriter(&writer);
+
+  DownloadActionTestAction test_action;
+  test_action.expected_input_object_ = install_plan;
+  BondActions(&feeder_action, &download_action);
+  BondActions(&download_action, &test_action);
+
+  ActionProcessor processor;
+  PassObjectOutTestProcessorDelegate delegate;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&download_action);
+  processor.EnqueueAction(&test_action);
+
+  loop.PostTask(FROM_HERE,
+                base::Bind([&processor] { processor.StartProcessing(); }));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+
+  EXPECT_EQ(true, test_action.did_run_);
+}
+
+TEST(DownloadActionTest, BadOutFileTest) {
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
+  DirectFileWriter writer;
+
+  // takes ownership of passed in HttpFetcher
+  InstallPlan install_plan(
+      false, false, "", 0, "", 0, "", path, "", "", "", "");
+  ObjectFeederAction<InstallPlan> feeder_action;
+  feeder_action.set_obj(install_plan);
+  MockPrefs prefs;
+  DownloadAction download_action(&prefs, nullptr,
+                                 new MockHttpFetcher("x", 1, nullptr));
+  download_action.SetTestFileWriter(&writer);
+
+  BondActions(&feeder_action, &download_action);
+
+  ActionProcessor processor;
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&download_action);
+  processor.StartProcessing();
+  ASSERT_FALSE(processor.IsRunning());
+
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+}
+
+// Test fixture for P2P tests.
+class P2PDownloadActionTest : public testing::Test {
+ protected:
+  P2PDownloadActionTest()
+    : start_at_offset_(0),
+      fake_um_(fake_system_state_.fake_clock()) {}
+
+  ~P2PDownloadActionTest() override {}
+
+  // Derived from testing::Test.
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  // Derived from testing::Test.
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  // To be called by tests to setup the download. The
+  // |starting_offset| parameter is for where to resume.
+  void SetupDownload(off_t starting_offset) {
+    start_at_offset_ = starting_offset;
+    // Prepare data 10 kB of data.
+    data_.clear();
+    for (unsigned int i = 0; i < 10 * 1000; i++)
+      data_ += 'a' + (i % 25);
+
+    // Setup p2p.
+    FakeP2PManagerConfiguration *test_conf = new FakeP2PManagerConfiguration();
+    p2p_manager_.reset(P2PManager::Construct(
+        test_conf, nullptr, &fake_um_, "cros_au", 3,
+        base::TimeDelta::FromDays(5)));
+    fake_system_state_.set_p2p_manager(p2p_manager_.get());
+  }
+
+  // To be called by tests to perform the download. The
+  // |use_p2p_to_share| parameter is used to indicate whether the
+  // payload should be shared via p2p.
+  void StartDownload(bool use_p2p_to_share) {
+    EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+                GetUsingP2PForSharing())
+        .WillRepeatedly(Return(use_p2p_to_share));
+
+    ScopedTempFile output_temp_file;
+    TestDirectFileWriter writer;
+    InstallPlan install_plan(false,
+                             false,
+                             "",
+                             data_.length(),
+                             "1234hash",
+                             0,
+                             "",
+                             output_temp_file.GetPath(),
+                             "",
+                             "",
+                             "",
+                             "");
+    ObjectFeederAction<InstallPlan> feeder_action;
+    feeder_action.set_obj(install_plan);
+    MockPrefs prefs;
+    http_fetcher_ = new MockHttpFetcher(data_.c_str(),
+                                        data_.length(),
+                                        nullptr);
+    // Note that DownloadAction takes ownership of the passed in HttpFetcher.
+    download_action_.reset(new DownloadAction(&prefs, &fake_system_state_,
+                                              http_fetcher_));
+    download_action_->SetTestFileWriter(&writer);
+    BondActions(&feeder_action, download_action_.get());
+    DownloadActionTestProcessorDelegate delegate(ErrorCode::kSuccess);
+    delegate.expected_data_ = chromeos::Blob(data_.begin() + start_at_offset_,
+                                             data_.end());
+    delegate.path_ = output_temp_file.GetPath();
+    processor_.set_delegate(&delegate);
+    processor_.EnqueueAction(&feeder_action);
+    processor_.EnqueueAction(download_action_.get());
+
+    loop_.PostTask(FROM_HERE, base::Bind(
+        &P2PDownloadActionTest::StartProcessorInRunLoopForP2P,
+        base::Unretained(this)));
+    loop_.Run();
+  }
+
+  // Mainloop used to make StartDownload() synchronous.
+  chromeos::FakeMessageLoop loop_{nullptr};
+
+  // The DownloadAction instance under test.
+  unique_ptr<DownloadAction> download_action_;
+
+  // The HttpFetcher used in the test.
+  MockHttpFetcher* http_fetcher_;
+
+  // The P2PManager used in the test.
+  unique_ptr<P2PManager> p2p_manager_;
+
+  // The ActionProcessor used for running the actions.
+  ActionProcessor processor_;
+
+  // A fake system state.
+  FakeSystemState fake_system_state_;
+
+  // The data being downloaded.
+  string data_;
+
+ private:
+  // Callback used in StartDownload() method.
+  void StartProcessorInRunLoopForP2P() {
+    processor_.StartProcessing();
+    http_fetcher_->SetOffset(start_at_offset_);
+  }
+
+  // The requested starting offset passed to SetupDownload().
+  off_t start_at_offset_;
+
+  chromeos_update_manager::FakeUpdateManager fake_um_;
+};
+
+TEST_F(P2PDownloadActionTest, IsWrittenTo) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(0);     // starting_offset
+  StartDownload(true);  // use_p2p_to_share
+
+  // Check the p2p file and its content matches what was sent.
+  string file_id = download_action_->p2p_file_id();
+  EXPECT_NE("", file_id);
+  EXPECT_EQ(data_.length(), p2p_manager_->FileGetSize(file_id));
+  EXPECT_EQ(data_.length(), p2p_manager_->FileGetExpectedSize(file_id));
+  string p2p_file_contents;
+  EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+                               &p2p_file_contents));
+  EXPECT_EQ(data_, p2p_file_contents);
+}
+
+TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(1000);  // starting_offset
+  StartDownload(true);  // use_p2p_to_share
+
+  // DownloadAction should convey that the file is not being shared.
+  // and that we don't have any p2p files.
+  EXPECT_EQ(download_action_->p2p_file_id(), "");
+  EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+TEST_F(P2PDownloadActionTest, CanAppend) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(1000);  // starting_offset
+
+  // Prepare the file with existing data before starting to write to
+  // it via DownloadAction.
+  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+  string existing_data;
+  for (unsigned int i = 0; i < 1000; i++)
+    existing_data += '0' + (i % 10);
+  ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+                      1000), 1000);
+
+  StartDownload(true);  // use_p2p_to_share
+
+  // DownloadAction should convey the same file_id and the file should
+  // have the expected size.
+  EXPECT_EQ(download_action_->p2p_file_id(), file_id);
+  EXPECT_EQ(p2p_manager_->FileGetSize(file_id), data_.length());
+  EXPECT_EQ(p2p_manager_->FileGetExpectedSize(file_id), data_.length());
+  string p2p_file_contents;
+  // Check that the first 1000 bytes wasn't touched and that we
+  // appended the remaining as appropriate.
+  EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+                               &p2p_file_contents));
+  EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000));
+  EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000));
+}
+
+TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
+  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  SetupDownload(1000);  // starting_offset
+
+  // Prepare the file with all existing data before starting to write
+  // to it via DownloadAction.
+  string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+  ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+  string existing_data;
+  for (unsigned int i = 0; i < 1000; i++)
+    existing_data += '0' + (i % 10);
+  ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+                      1000), 1000);
+
+  // Check that the file is there.
+  EXPECT_EQ(p2p_manager_->FileGetSize(file_id), 1000);
+  EXPECT_EQ(p2p_manager_->CountSharedFiles(), 1);
+
+  StartDownload(false);  // use_p2p_to_share
+
+  // DownloadAction should have deleted the p2p file. Check that it's gone.
+  EXPECT_EQ(p2p_manager_->FileGetSize(file_id), -1);
+  EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/error_code.h b/error_code.h
new file mode 100644
index 0000000..fde1833
--- /dev/null
+++ b/error_code.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2013 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_ERROR_CODE_H_
+#define UPDATE_ENGINE_ERROR_CODE_H_
+
+#include <ostream>  // NOLINT(readability/streams)
+
+namespace chromeos_update_engine {
+
+// Action exit codes.
+enum class ErrorCode : int {
+  kSuccess = 0,
+  kError = 1,
+  kOmahaRequestError = 2,
+  kOmahaResponseHandlerError = 3,
+  kFilesystemCopierError = 4,
+  kPostinstallRunnerError = 5,
+  kPayloadMismatchedType = 6,
+  kInstallDeviceOpenError = 7,
+  kKernelDeviceOpenError = 8,
+  kDownloadTransferError = 9,
+  kPayloadHashMismatchError = 10,
+  kPayloadSizeMismatchError = 11,
+  kDownloadPayloadVerificationError = 12,
+  kDownloadNewPartitionInfoError = 13,
+  kDownloadWriteError = 14,
+  kNewRootfsVerificationError = 15,
+  kNewKernelVerificationError = 16,
+  kSignedDeltaPayloadExpectedError = 17,
+  kDownloadPayloadPubKeyVerificationError = 18,
+  kPostinstallBootedFromFirmwareB = 19,
+  kDownloadStateInitializationError = 20,
+  kDownloadInvalidMetadataMagicString = 21,
+  kDownloadSignatureMissingInManifest = 22,
+  kDownloadManifestParseError = 23,
+  kDownloadMetadataSignatureError = 24,
+  kDownloadMetadataSignatureVerificationError = 25,
+  kDownloadMetadataSignatureMismatch = 26,
+  kDownloadOperationHashVerificationError = 27,
+  kDownloadOperationExecutionError = 28,
+  kDownloadOperationHashMismatch = 29,
+  kOmahaRequestEmptyResponseError = 30,
+  kOmahaRequestXMLParseError = 31,
+  kDownloadInvalidMetadataSize = 32,
+  kDownloadInvalidMetadataSignature = 33,
+  kOmahaResponseInvalid = 34,
+  kOmahaUpdateIgnoredPerPolicy = 35,
+  kOmahaUpdateDeferredPerPolicy = 36,
+  kOmahaErrorInHTTPResponse = 37,
+  kDownloadOperationHashMissingError = 38,
+  kDownloadMetadataSignatureMissingError = 39,
+  kOmahaUpdateDeferredForBackoff = 40,
+  kPostinstallPowerwashError = 41,
+  kUpdateCanceledByChannelChange = 42,
+  kPostinstallFirmwareRONotUpdatable = 43,
+  kUnsupportedMajorPayloadVersion = 44,
+  kUnsupportedMinorPayloadVersion = 45,
+  kOmahaRequestXMLHasEntityDecl = 46,
+  kFilesystemVerifierError = 47,
+
+  // VERY IMPORTANT! When adding new error codes:
+  //
+  // 1) Update tools/metrics/histograms/histograms.xml in Chrome.
+  //
+  // 2) Update the assorted switch statements in update_engine which won't
+  //    build until this case is added.
+
+  // Any code above this is sent to both Omaha and UMA as-is, except
+  // kOmahaErrorInHTTPResponse (see error code 2000 for more details).
+  // Codes/flags below this line is sent only to Omaha and not to UMA.
+
+  // kUmaReportedMax is not an error code per se, it's just the count
+  // of the number of enums above.  Add any new errors above this line if you
+  // want them to show up on UMA. Stuff below this line will not be sent to UMA
+  // but is used for other errors that are sent to Omaha. We don't assign any
+  // particular value for this enum so that it's just one more than the last
+  // one above and thus always represents the correct count of UMA metrics
+  // buckets, even when new enums are added above this line in future. See
+  // utils::SendErrorCodeToUma on how this enum is used.
+  kUmaReportedMax,
+
+  // use the 2xxx range to encode HTTP errors. These errors are available in
+  // Dremel with the individual granularity. But for UMA purposes, all these
+  // errors are aggregated into one: kOmahaErrorInHTTPResponse.
+  kOmahaRequestHTTPResponseBase = 2000,  // + HTTP response code
+
+  // TODO(jaysri): Move out all the bit masks into separate constants
+  // outside the enum as part of fixing bug 34369.
+  // Bit flags. Remember to update the mask below for new bits.
+
+  // Set if boot mode not normal.
+  // TODO(garnold) This is very debatable value to use, knowing that the
+  // underlying type is a signed int (often, 32-bit). However, at this point
+  // there are parts of the ecosystem that expect this to be a negative value,
+  // so we preserve this semantics. This should be reconsidered if/when we
+  // modify the implementation of ErrorCode into a properly encapsulated class.
+  kDevModeFlag = 1 << 31,
+
+  // Set if resuming an interruped update.
+  kResumedFlag = 1 << 30,
+
+  // Set if using a dev/test image as opposed to an MP-signed image.
+  kTestImageFlag = 1 << 29,
+
+  // Set if using devserver or Omaha sandbox (using crosh autest).
+  kTestOmahaUrlFlag = 1 << 28,
+
+  // Mask that indicates bit positions that are used to indicate special flags
+  // that are embedded in the error code to provide additional context about
+  // the system in which the error was encountered.
+  kSpecialFlags = (kDevModeFlag | kResumedFlag | kTestImageFlag |
+                   kTestOmahaUrlFlag)
+};
+
+inline std::ostream& operator<<(std::ostream& os, ErrorCode val) {
+  return os << static_cast<int>(val);
+}
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_ERROR_CODE_H_
diff --git a/extent_writer.cc b/extent_writer.cc
new file mode 100644
index 0000000..336311d
--- /dev/null
+++ b/extent_writer.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2009 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/extent_writer.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/utils.h"
+
+using std::min;
+
+namespace chromeos_update_engine {
+
+bool DirectExtentWriter::Write(const void* bytes, size_t count) {
+  if (count == 0)
+    return true;
+  const char* c_bytes = reinterpret_cast<const char*>(bytes);
+  size_t bytes_written = 0;
+  while (count - bytes_written > 0) {
+    TEST_AND_RETURN_FALSE(next_extent_index_ < extents_.size());
+    uint64_t bytes_remaining_next_extent =
+        extents_[next_extent_index_].num_blocks() * block_size_ -
+        extent_bytes_written_;
+    CHECK_NE(bytes_remaining_next_extent, static_cast<uint64_t>(0));
+    size_t bytes_to_write =
+        static_cast<size_t>(min(static_cast<uint64_t>(count - bytes_written),
+                                bytes_remaining_next_extent));
+    TEST_AND_RETURN_FALSE(bytes_to_write > 0);
+
+    if (extents_[next_extent_index_].start_block() != kSparseHole) {
+      const off64_t offset =
+          extents_[next_extent_index_].start_block() * block_size_ +
+          extent_bytes_written_;
+      TEST_AND_RETURN_FALSE_ERRNO(fd_->Seek(offset, SEEK_SET) !=
+                                  static_cast<off64_t>(-1));
+      TEST_AND_RETURN_FALSE(
+          utils::WriteAll(fd_, c_bytes + bytes_written, bytes_to_write));
+    }
+    bytes_written += bytes_to_write;
+    extent_bytes_written_ += bytes_to_write;
+    if (bytes_remaining_next_extent == bytes_to_write) {
+      // We filled this extent
+      CHECK_EQ(extent_bytes_written_,
+               extents_[next_extent_index_].num_blocks() * block_size_);
+      // move to next extent
+      extent_bytes_written_ = 0;
+      next_extent_index_++;
+    }
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/extent_writer.h b/extent_writer.h
new file mode 100644
index 0000000..765bff6
--- /dev/null
+++ b/extent_writer.h
@@ -0,0 +1,129 @@
+// Copyright (c) 2009 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_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_EXTENT_WRITER_H_
+
+#include <vector>
+
+#include <base/logging.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+// ExtentWriter is an abstract class which synchronously writes to a given
+// file descriptor at the extents given.
+
+namespace chromeos_update_engine {
+
+class ExtentWriter {
+ public:
+  ExtentWriter() : end_called_(false) {}
+  virtual ~ExtentWriter() {
+    LOG_IF(ERROR, !end_called_) << "End() not called on ExtentWriter.";
+  }
+
+  // Returns true on success.
+  virtual bool Init(FileDescriptorPtr fd,
+                    const std::vector<Extent>& extents,
+                    uint32_t block_size) = 0;
+
+  // Returns true on success.
+  virtual bool Write(const void* bytes, size_t count) = 0;
+
+  // Should be called when all writing is complete. Returns true on success.
+  // The fd is not closed. Caller is responsible for closing it.
+  bool End() {
+    end_called_ = true;
+    return EndImpl();
+  }
+  virtual bool EndImpl() = 0;
+ private:
+  bool end_called_;
+};
+
+// DirectExtentWriter is probably the simplest ExtentWriter implementation.
+// It writes the data directly into the extents.
+
+class DirectExtentWriter : public ExtentWriter {
+ public:
+  DirectExtentWriter()
+      : fd_(nullptr),
+        block_size_(0),
+        extent_bytes_written_(0),
+        next_extent_index_(0) {}
+  ~DirectExtentWriter() {}
+
+  bool Init(FileDescriptorPtr fd,
+            const std::vector<Extent>& extents,
+            uint32_t block_size) {
+    fd_ = fd;
+    block_size_ = block_size;
+    extents_ = extents;
+    return true;
+  }
+  bool Write(const void* bytes, size_t count);
+  bool EndImpl() {
+    return true;
+  }
+
+ private:
+  FileDescriptorPtr fd_;
+
+  size_t block_size_;
+  // Bytes written into next_extent_index_ thus far
+  uint64_t extent_bytes_written_;
+  std::vector<Extent> extents_;
+  // The next call to write should correspond to extents_[next_extent_index_]
+  std::vector<Extent>::size_type next_extent_index_;
+};
+
+// Takes an underlying ExtentWriter to which all operations are delegated.
+// When End() is called, ZeroPadExtentWriter ensures that the total number
+// of bytes written is a multiple of block_size_. If not, it writes zeros
+// to pad as needed.
+
+class ZeroPadExtentWriter : public ExtentWriter {
+ public:
+  explicit ZeroPadExtentWriter(ExtentWriter* underlying_extent_writer)
+      : underlying_extent_writer_(underlying_extent_writer),
+        block_size_(0),
+        bytes_written_mod_block_size_(0) {}
+  ~ZeroPadExtentWriter() {}
+
+  bool Init(FileDescriptorPtr fd,
+            const std::vector<Extent>& extents,
+            uint32_t block_size) {
+    block_size_ = block_size;
+    return underlying_extent_writer_->Init(fd, extents, block_size);
+  }
+  bool Write(const void* bytes, size_t count) {
+    if (underlying_extent_writer_->Write(bytes, count)) {
+      bytes_written_mod_block_size_ += count;
+      bytes_written_mod_block_size_ %= block_size_;
+      return true;
+    }
+    return false;
+  }
+  bool EndImpl() {
+    if (bytes_written_mod_block_size_) {
+      const size_t write_size = block_size_ - bytes_written_mod_block_size_;
+      chromeos::Blob zeros(write_size, 0);
+      TEST_AND_RETURN_FALSE(underlying_extent_writer_->Write(zeros.data(),
+                                                             write_size));
+    }
+    return underlying_extent_writer_->End();
+  }
+
+ private:
+  ExtentWriter* underlying_extent_writer_;  // The underlying ExtentWriter.
+  size_t block_size_;
+  size_t bytes_written_mod_block_size_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_EXTENT_WRITER_H_
diff --git a/extent_writer_unittest.cc b/extent_writer_unittest.cc
new file mode 100644
index 0000000..f6519b6
--- /dev/null
+++ b/extent_writer_unittest.cc
@@ -0,0 +1,257 @@
+// Copyright (c) 2009 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/extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::ExpectVectorsEq;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64_bit);
+
+namespace {
+const char kPathTemplate[] = "./ExtentWriterTest-file.XXXXXX";
+const size_t kBlockSize = 4096;
+}
+
+class ExtentWriterTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+    fd_.reset(new EintrSafeFileDescriptor);
+    int fd = mkstemp(path_);
+    ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+    close(fd);
+  }
+  void TearDown() override {
+    fd_->Close();
+    unlink(path_);
+  }
+
+  // Writes data to an extent writer in 'chunk_size' chunks with
+  // the first chunk of size first_chunk_size. It calculates what the
+  // resultant file should look like and ensure that the extent writer
+  // wrote the file correctly.
+  void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+  void TestZeroPad(bool aligned_size);
+
+  FileDescriptorPtr fd_;
+  char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(ExtentWriterTest, SimpleTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  const string bytes = "1234";
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+  EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
+  EXPECT_TRUE(direct_writer.End());
+
+  EXPECT_EQ(kBlockSize + bytes.size(), utils::FileSize(path_));
+
+  chromeos::Blob result_file;
+  EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+  chromeos::Blob expected_file(kBlockSize);
+  expected_file.insert(expected_file.end(),
+                       bytes.data(), bytes.data() + bytes.size());
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroLengthTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+  EXPECT_TRUE(direct_writer.Write(nullptr, 0));
+  EXPECT_TRUE(direct_writer.End());
+}
+
+TEST_F(ExtentWriterTest, OverflowExtentTest) {
+  WriteAlignedExtents(kBlockSize * 3, kBlockSize * 3);
+}
+
+TEST_F(ExtentWriterTest, UnalignedWriteTest) {
+  WriteAlignedExtents(7, 7);
+}
+
+TEST_F(ExtentWriterTest, LargeUnalignedWriteTest) {
+  WriteAlignedExtents(kBlockSize * 2, kBlockSize / 2);
+}
+
+void ExtentWriterTest::WriteAlignedExtents(size_t chunk_size,
+                                           size_t first_chunk_size) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(2);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  chromeos::Blob data(kBlockSize * 3);
+  test_utils::FillWithData(&data);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+  size_t bytes_written = 0;
+  while (bytes_written < data.size()) {
+    size_t bytes_to_write = min(data.size() - bytes_written, chunk_size);
+    if (bytes_written == 0) {
+      bytes_to_write = min(data.size() - bytes_written, first_chunk_size);
+    }
+    EXPECT_TRUE(direct_writer.Write(&data[bytes_written], bytes_to_write));
+    bytes_written += bytes_to_write;
+  }
+  EXPECT_TRUE(direct_writer.End());
+
+  EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+  chromeos::Blob result_file;
+  EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+  chromeos::Blob expected_file;
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize,
+                       data.begin() + kBlockSize * 2);
+  expected_file.insert(expected_file.end(),
+                       data.begin(), data.begin() + kBlockSize);
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize * 2, data.end());
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadNullTest) {
+  TestZeroPad(true);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadFillTest) {
+  TestZeroPad(false);
+}
+
+void ExtentWriterTest::TestZeroPad(bool aligned_size) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+
+  chromeos::Blob data(kBlockSize * 2);
+  test_utils::FillWithData(&data);
+
+  DirectExtentWriter direct_writer;
+  ZeroPadExtentWriter zero_pad_writer(&direct_writer);
+
+  EXPECT_TRUE(zero_pad_writer.Init(fd_, extents, kBlockSize));
+  size_t bytes_to_write = data.size();
+  const size_t missing_bytes = (aligned_size ? 0 : 9);
+  bytes_to_write -= missing_bytes;
+  fd_->Seek(kBlockSize - missing_bytes, SEEK_SET);
+  EXPECT_EQ(3, fd_->Write("xxx", 3));
+  ASSERT_TRUE(zero_pad_writer.Write(data.data(), bytes_to_write));
+  EXPECT_TRUE(zero_pad_writer.End());
+
+  EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+  chromeos::Blob result_file;
+  EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+  chromeos::Blob expected_file;
+  expected_file.insert(expected_file.end(),
+                       data.begin() + kBlockSize,
+                       data.begin() + kBlockSize * 2);
+  expected_file.insert(expected_file.end(),
+                       data.begin(), data.begin() + kBlockSize);
+  if (missing_bytes) {
+    memset(&expected_file[kBlockSize - missing_bytes], 0, missing_bytes);
+  }
+
+  ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, SparseFileTest) {
+  vector<Extent> extents;
+  Extent extent;
+  extent.set_start_block(1);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  extent.set_start_block(kSparseHole);
+  extent.set_num_blocks(2);
+  extents.push_back(extent);
+  extent.set_start_block(0);
+  extent.set_num_blocks(1);
+  extents.push_back(extent);
+  const int block_count = 4;
+  const int on_disk_count = 2;
+
+  chromeos::Blob data(17);
+  test_utils::FillWithData(&data);
+
+  DirectExtentWriter direct_writer;
+  EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+  size_t bytes_written = 0;
+  while (bytes_written < (block_count * kBlockSize)) {
+    size_t bytes_to_write = min(block_count * kBlockSize - bytes_written,
+                                data.size());
+    EXPECT_TRUE(direct_writer.Write(data.data(), bytes_to_write));
+    bytes_written += bytes_to_write;
+  }
+  EXPECT_TRUE(direct_writer.End());
+
+  // check file size, then data inside
+  ASSERT_EQ(2 * kBlockSize, utils::FileSize(path_));
+
+  chromeos::Blob resultant_data;
+  EXPECT_TRUE(utils::ReadFile(path_, &resultant_data));
+
+  // Create expected data
+  chromeos::Blob expected_data(on_disk_count * kBlockSize);
+  chromeos::Blob big(block_count * kBlockSize);
+  for (chromeos::Blob::size_type i = 0; i < big.size(); i++) {
+    big[i] = data[i % data.size()];
+  }
+  memcpy(&expected_data[kBlockSize], &big[0], kBlockSize);
+  memcpy(&expected_data[0], &big[3 * kBlockSize], kBlockSize);
+  ExpectVectorsEq(expected_data, resultant_data);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/fake_clock.h b/fake_clock.h
new file mode 100644
index 0000000..c717bdf
--- /dev/null
+++ b/fake_clock.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2013 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_FAKE_CLOCK_H_
+#define UPDATE_ENGINE_FAKE_CLOCK_H_
+
+#include "update_engine/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock that can be made to tell any time you want.
+class FakeClock : public ClockInterface {
+ public:
+  FakeClock() {}
+
+  base::Time GetWallclockTime() override {
+    return wallclock_time_;
+  }
+
+  base::Time GetMonotonicTime() override {
+    return monotonic_time_;
+  }
+
+  base::Time GetBootTime() override {
+    return boot_time_;
+  }
+
+  void SetWallclockTime(const base::Time &time) {
+    wallclock_time_ = time;
+  }
+
+  void SetMonotonicTime(const base::Time &time) {
+    monotonic_time_ = time;
+  }
+
+  void SetBootTime(const base::Time &time) {
+    boot_time_ = time;
+  }
+
+ private:
+  base::Time wallclock_time_;
+  base::Time monotonic_time_;
+  base::Time boot_time_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeClock);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_CLOCK_H_
diff --git a/fake_file_writer.h b/fake_file_writer.h
new file mode 100644
index 0000000..751cbcf
--- /dev/null
+++ b/fake_file_writer.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2009 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_FAKE_FILE_WRITER_H_
+#define UPDATE_ENGINE_FAKE_FILE_WRITER_H_
+
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/file_writer.h"
+
+// FakeFileWriter is an implementation of FileWriter. It will succeed
+// calls to Open(), Close(), but not do any work. All calls to Write()
+// will append the passed data to an internal vector.
+
+namespace chromeos_update_engine {
+
+class FakeFileWriter : public FileWriter {
+ public:
+  FakeFileWriter() : was_opened_(false), was_closed_(false) {}
+
+  virtual int Open(const char* path, int flags, mode_t mode) {
+    CHECK(!was_opened_);
+    CHECK(!was_closed_);
+    was_opened_ = true;
+    return 0;
+  }
+
+  virtual ssize_t Write(const void* bytes, size_t count) {
+    CHECK(was_opened_);
+    CHECK(!was_closed_);
+    const char* char_bytes = reinterpret_cast<const char*>(bytes);
+    bytes_.insert(bytes_.end(), char_bytes, char_bytes + count);
+    return count;
+  }
+
+  virtual int Close() {
+    CHECK(was_opened_);
+    CHECK(!was_closed_);
+    was_closed_ = true;
+    return 0;
+  }
+
+  const chromeos::Blob& bytes() {
+    return bytes_;
+  }
+
+ private:
+  // The internal store of all bytes that have been written
+  chromeos::Blob bytes_;
+
+  // These are just to ensure FileWriter methods are called properly.
+  bool was_opened_;
+  bool was_closed_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeFileWriter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_FILE_WRITER_H_
diff --git a/fake_hardware.h b/fake_hardware.h
new file mode 100644
index 0000000..13c3341
--- /dev/null
+++ b/fake_hardware.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2013 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_FAKE_HARDWARE_H_
+#define UPDATE_ENGINE_FAKE_HARDWARE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+#include "update_engine/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake hardware interface used for testing.
+class FakeHardware : public HardwareInterface {
+ public:
+  // Value used to signal that the powerwash_count file is not present. When
+  // this value is used in SetPowerwashCount(), GetPowerwashCount() will return
+  // false.
+  static const int kPowerwashCountNotSet = -1;
+
+  FakeHardware()
+    : kernel_device_("/dev/sdz4"),
+      boot_device_("/dev/sdz5"),
+      is_boot_device_removable_(false),
+      kernel_devices_({"/dev/sdz2", "/dev/sdz4"}),
+      is_official_build_(true),
+      is_normal_boot_mode_(true),
+      is_oobe_complete_(false),
+      hardware_class_("Fake HWID BLAH-1234"),
+      firmware_version_("Fake Firmware v1.0.1"),
+      ec_version_("Fake EC v1.0a"),
+      powerwash_count_(kPowerwashCountNotSet) {}
+
+  // HardwareInterface methods.
+  std::string BootKernelDevice() const override { return kernel_device_; }
+
+  std::string BootDevice() const override { return boot_device_; }
+
+  bool IsBootDeviceRemovable() const override {
+    return is_boot_device_removable_;
+  }
+
+  std::vector<std::string> GetKernelDevices() const override {
+    return kernel_devices_;
+  }
+
+  bool IsKernelBootable(const std::string& kernel_device,
+                        bool* bootable) const override {
+    auto i = is_bootable_.find(kernel_device);
+    *bootable = (i != is_bootable_.end()) ? i->second : true;
+    return true;
+  }
+
+  bool MarkKernelUnbootable(const std::string& kernel_device) override {
+    is_bootable_[kernel_device] = false;
+    return true;
+  }
+
+  bool IsOfficialBuild() const override { return is_official_build_; }
+
+  bool IsNormalBootMode() const override { return is_normal_boot_mode_; }
+
+  bool IsOOBEComplete(base::Time* out_time_of_oobe) const override {
+    if (out_time_of_oobe != nullptr)
+      *out_time_of_oobe = oobe_timestamp_;
+    return is_oobe_complete_;
+  }
+
+  std::string GetHardwareClass() const override { return hardware_class_; }
+
+  std::string GetFirmwareVersion() const override { return firmware_version_; }
+
+  std::string GetECVersion() const override { return ec_version_; }
+
+  int GetPowerwashCount() const override { return powerwash_count_; }
+
+  // Setters
+  void SetBootDevice(const std::string& boot_device) {
+    boot_device_ = boot_device;
+  }
+
+  void SetIsBootDeviceRemovable(bool is_boot_device_removable) {
+    is_boot_device_removable_ = is_boot_device_removable;
+  }
+
+  void SetIsOfficialBuild(bool is_official_build) {
+    is_official_build_ = is_official_build;
+  }
+
+  void SetIsNormalBootMode(bool is_normal_boot_mode) {
+    is_normal_boot_mode_ = is_normal_boot_mode;
+  }
+
+  // Sets the IsOOBEComplete to True with the given timestamp.
+  void SetIsOOBEComplete(base::Time oobe_timestamp) {
+    is_oobe_complete_ = true;
+    oobe_timestamp_ = oobe_timestamp;
+  }
+
+  // Sets the IsOOBEComplete to False.
+  void UnsetIsOOBEComplete() {
+    is_oobe_complete_ = false;
+  }
+
+  void SetHardwareClass(std::string hardware_class) {
+    hardware_class_ = hardware_class;
+  }
+
+  void SetFirmwareVersion(std::string firmware_version) {
+    firmware_version_ = firmware_version;
+  }
+
+  void SetECVersion(std::string ec_version) {
+    ec_version_ = ec_version;
+  }
+
+  void SetPowerwashCount(int powerwash_count) {
+    powerwash_count_ = powerwash_count;
+  }
+
+ private:
+  std::string kernel_device_;
+  std::string boot_device_;
+  bool is_boot_device_removable_;
+  std::vector<std::string>  kernel_devices_;
+  std::map<std::string, bool> is_bootable_;
+  bool is_official_build_;
+  bool is_normal_boot_mode_;
+  bool is_oobe_complete_;
+  base::Time oobe_timestamp_;
+  std::string hardware_class_;
+  std::string firmware_version_;
+  std::string ec_version_;
+  int powerwash_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeHardware);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_HARDWARE_H_
diff --git a/fake_p2p_manager.h b/fake_p2p_manager.h
new file mode 100644
index 0000000..ce214e4
--- /dev/null
+++ b/fake_p2p_manager.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2013 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_FAKE_P2P_MANAGER_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/p2p_manager.h"
+
+namespace chromeos_update_engine {
+
+// A fake implementation of P2PManager.
+class FakeP2PManager : public P2PManager {
+ public:
+  FakeP2PManager() :
+    is_p2p_enabled_(false),
+    ensure_p2p_running_result_(false),
+    ensure_p2p_not_running_result_(false),
+    perform_housekeeping_result_(false),
+    count_shared_files_result_(0) {}
+
+  // P2PManager overrides.
+  void SetDevicePolicy(const policy::DevicePolicy* device_policy) override {}
+
+  bool IsP2PEnabled() override {
+    return is_p2p_enabled_;
+  }
+
+  bool EnsureP2PRunning() override {
+    return ensure_p2p_running_result_;
+  }
+
+  bool EnsureP2PNotRunning() override {
+    return ensure_p2p_not_running_result_;
+  }
+
+  bool PerformHousekeeping() override {
+    return perform_housekeeping_result_;
+  }
+
+  void LookupUrlForFile(const std::string& file_id,
+                        size_t minimum_size,
+                        base::TimeDelta max_time_to_wait,
+                        LookupCallback callback) override {
+    callback.Run(lookup_url_for_file_result_);
+  }
+
+  bool FileShare(const std::string& file_id,
+                 size_t expected_size) override {
+    return false;
+  }
+
+  base::FilePath FileGetPath(const std::string& file_id) override {
+    return base::FilePath();
+  }
+
+  ssize_t FileGetSize(const std::string& file_id) override {
+    return -1;
+  }
+
+  ssize_t FileGetExpectedSize(const std::string& file_id) override {
+    return -1;
+  }
+
+  bool FileGetVisible(const std::string& file_id,
+                      bool *out_result) override {
+    return false;
+  }
+
+  bool FileMakeVisible(const std::string& file_id) override {
+    return false;
+  }
+
+  int CountSharedFiles() override {
+    return count_shared_files_result_;
+  }
+
+  // Methods for controlling what the fake returns and how it acts.
+  void SetP2PEnabled(bool is_p2p_enabled) {
+    is_p2p_enabled_ = is_p2p_enabled;
+  }
+
+  void SetEnsureP2PRunningResult(bool ensure_p2p_running_result) {
+    ensure_p2p_running_result_ = ensure_p2p_running_result;
+  }
+
+  void SetEnsureP2PNotRunningResult(bool ensure_p2p_not_running_result) {
+    ensure_p2p_not_running_result_ = ensure_p2p_not_running_result;
+  }
+
+  void SetPerformHousekeepingResult(bool perform_housekeeping_result) {
+    perform_housekeeping_result_ = perform_housekeeping_result;
+  }
+
+  void SetCountSharedFilesResult(int count_shared_files_result) {
+    count_shared_files_result_ = count_shared_files_result;
+  }
+
+  void SetLookupUrlForFileResult(const std::string& url) {
+    lookup_url_for_file_result_ = url;
+  }
+
+ private:
+  bool is_p2p_enabled_;
+  bool ensure_p2p_running_result_;
+  bool ensure_p2p_not_running_result_;
+  bool perform_housekeeping_result_;
+  int count_shared_files_result_;
+  std::string lookup_url_for_file_result_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeP2PManager);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
diff --git a/fake_p2p_manager_configuration.h b/fake_p2p_manager_configuration.h
new file mode 100644
index 0000000..1dc7c6e
--- /dev/null
+++ b/fake_p2p_manager_configuration.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2013 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_FAKE_P2P_MANAGER_CONFIGURATION_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
+
+#include "update_engine/p2p_manager.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/string_number_conversions.h>
+
+namespace chromeos_update_engine {
+
+// Configuration for P2PManager for use in unit tests. Instead of
+// /var/cache/p2p, a temporary directory is used.
+class FakeP2PManagerConfiguration : public P2PManager::Configuration {
+ public:
+  FakeP2PManagerConfiguration() {
+    EXPECT_TRUE(utils::MakeTempDirectory("/tmp/p2p-tc.XXXXXX", &p2p_dir_));
+  }
+
+  ~FakeP2PManagerConfiguration() {
+    if (p2p_dir_.size() > 0 && !test_utils::RecursiveUnlinkDir(p2p_dir_)) {
+      PLOG(ERROR) << "Unable to unlink files and directory in " << p2p_dir_;
+    }
+  }
+
+  // P2PManager::Configuration override
+  base::FilePath GetP2PDir() override {
+    return base::FilePath(p2p_dir_);
+  }
+
+  // P2PManager::Configuration override
+  std::vector<std::string> GetInitctlArgs(bool is_start) override {
+    return is_start ? initctl_start_args_ : initctl_stop_args_;
+  }
+
+  // P2PManager::Configuration override
+  std::vector<std::string> GetP2PClientArgs(const std::string &file_id,
+                                            size_t minimum_size) override {
+    std::vector<std::string> formatted_command = p2p_client_cmd_format_;
+    // Replace {variable} on the passed string.
+    std::string str_minimum_size = base::SizeTToString(minimum_size);
+    for (std::string& arg : formatted_command) {
+      ReplaceSubstringsAfterOffset(&arg, 0, "{file_id}", file_id);
+      ReplaceSubstringsAfterOffset(&arg, 0, "{minsize}", str_minimum_size);
+    }
+    return formatted_command;
+  }
+
+  // Use |command_line| instead of "initctl start p2p" when attempting
+  // to start the p2p service.
+  void SetInitctlStartCommand(const std::vector<std::string>& command) {
+    initctl_start_args_ = command;
+  }
+
+  // Use |command_line| instead of "initctl stop p2p" when attempting
+  // to stop the p2p service.
+  void SetInitctlStopCommand(const std::vector<std::string>& command) {
+    initctl_stop_args_ = command;
+  }
+
+  // Use |command_format| instead of "p2p-client --get-url={file_id}
+  // --minimum-size={minsize}" when attempting to look up a file using
+  // p2p-client(1).
+  //
+  // The passed |command_format| argument can have "{file_id}" and "{minsize}"
+  // as substrings of any of its elements, that will be replaced by the
+  // corresponding values passed to GetP2PClientArgs().
+  void SetP2PClientCommand(const std::vector<std::string>& command_format) {
+    p2p_client_cmd_format_ = command_format;
+  }
+
+ private:
+  // The temporary directory used for p2p.
+  std::string p2p_dir_;
+
+  // Argument vector for starting p2p.
+  std::vector<std::string> initctl_start_args_{"initctl", "start", "p2p"};
+
+  // Argument vector for stopping p2p.
+  std::vector<std::string> initctl_stop_args_{"initctl", "stop", "p2p"};
+
+  // A string for generating the p2p-client command. See the
+  // SetP2PClientCommandLine() for details.
+  std::vector<std::string> p2p_client_cmd_format_{
+      "p2p-client", "--get-url={file_id}", "--minimum-size={minsize}"};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeP2PManagerConfiguration);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
diff --git a/fake_prefs.cc b/fake_prefs.cc
new file mode 100644
index 0000000..64ff18a
--- /dev/null
+++ b/fake_prefs.cc
@@ -0,0 +1,122 @@
+// Copyright 2014 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/fake_prefs.h"
+
+#include <gtest/gtest.h>
+
+using std::string;
+
+using chromeos_update_engine::FakePrefs;
+
+namespace {
+
+void CheckNotNull(const string& key, void* ptr) {
+  EXPECT_NE(nullptr, ptr)
+      << "Called Get*() for key \"" << key << "\" with a null parameter.";
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+// Compile-time type-dependent constants definitions.
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<string>::type =
+    FakePrefs::PrefType::kString;
+template<>
+string FakePrefs::PrefValue::* const  // NOLINT(runtime/string), not static str.
+    FakePrefs::PrefConsts<string>::member = &FakePrefs::PrefValue::as_str;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<int64_t>::type =
+    FakePrefs::PrefType::kInt64;
+template<>
+int64_t FakePrefs::PrefValue::* const FakePrefs::PrefConsts<int64_t>::member =
+    &FakePrefs::PrefValue::as_int64;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<bool>::type =
+    FakePrefs::PrefType::kBool;
+template<>
+bool FakePrefs::PrefValue::* const FakePrefs::PrefConsts<bool>::member =
+    &FakePrefs::PrefValue::as_bool;
+
+
+bool FakePrefs::GetString(const string& key, string* value) {
+  return GetValue(key, value);
+}
+
+bool FakePrefs::SetString(const string& key, const string& value) {
+  SetValue(key, value);
+  return true;
+}
+
+bool FakePrefs::GetInt64(const string& key, int64_t* value) {
+  return GetValue(key, value);
+}
+
+bool FakePrefs::SetInt64(const string& key, const int64_t value) {
+  SetValue(key, value);
+  return true;
+}
+
+bool FakePrefs::GetBoolean(const string& key, bool* value) {
+  return GetValue(key, value);
+}
+
+bool FakePrefs::SetBoolean(const string& key, const bool value) {
+  SetValue(key, value);
+  return true;
+}
+
+bool FakePrefs::Exists(const string& key) {
+  return values_.find(key) != values_.end();
+}
+
+bool FakePrefs::Delete(const string& key) {
+  if (values_.find(key) == values_.end())
+    return false;
+  values_.erase(key);
+  return true;
+}
+
+string FakePrefs::GetTypeName(PrefType type) {
+  switch (type) {
+    case PrefType::kString:
+      return "string";
+    case PrefType::kInt64:
+      return "int64_t";
+    case PrefType::kBool:
+      return "bool";
+  }
+  return "Unknown";
+}
+
+void FakePrefs::CheckKeyType(const string& key, PrefType type) const {
+  auto it = values_.find(key);
+  EXPECT_TRUE(it == values_.end() || it->second.type == type)
+      << "Key \"" << key << "\" if defined as " << GetTypeName(it->second.type)
+      << " but is accessed as a " << GetTypeName(type);
+}
+
+template<typename T>
+void FakePrefs::SetValue(const string& key, const T& value) {
+  CheckKeyType(key, PrefConsts<T>::type);
+  values_[key].type = PrefConsts<T>::type;
+  values_[key].value.*(PrefConsts<T>::member) = value;
+}
+
+template<typename T>
+bool FakePrefs::GetValue(const string& key, T* value) const {
+  CheckKeyType(key, PrefConsts<T>::type);
+  auto it = values_.find(key);
+  if (it == values_.end())
+    return false;
+  CheckNotNull(key, value);
+  *value = it->second.value.*(PrefConsts<T>::member);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/fake_prefs.h b/fake_prefs.h
new file mode 100644
index 0000000..f4174bf
--- /dev/null
+++ b/fake_prefs.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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_FAKE_PREFS_H_
+#define UPDATE_ENGINE_FAKE_PREFS_H_
+
+#include <map>
+#include <string>
+
+#include <base/macros.h>
+
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake preference store by storing the value associated with
+// a key in a std::map, suitable for testing. It doesn't allow to set a value on
+// a key with a different type than the previously set type. This enforces the
+// type of a given key to be fixed. Also the class checks that the Get*()
+// methods aren't called on a key set with a different type.
+
+class FakePrefs : public PrefsInterface {
+ public:
+  FakePrefs() {}
+
+  // PrefsInterface methods.
+  bool GetString(const std::string& key, std::string* value) override;
+  bool SetString(const std::string& key, const std::string& value) override;
+  bool GetInt64(const std::string& key, int64_t* value) override;
+  bool SetInt64(const std::string& key, const int64_t value) override;
+  bool GetBoolean(const std::string& key, bool* value) override;
+  bool SetBoolean(const std::string& key, const bool value) override;
+
+  bool Exists(const std::string& key) override;
+  bool Delete(const std::string& key) override;
+
+ private:
+  enum class PrefType {
+    kString,
+    kInt64,
+    kBool,
+  };
+  struct PrefValue {
+    std::string as_str;
+    int64_t as_int64;
+    bool as_bool;
+  };
+
+  struct PrefTypeValue {
+    PrefType type;
+    PrefValue value;
+  };
+
+  // Class to store compile-time type-dependent constants.
+  template<typename T>
+  class PrefConsts {
+   public:
+    // The PrefType associated with T.
+    static FakePrefs::PrefType const type;
+
+    // The data member pointer to PrefValue associated with T.
+    static T FakePrefs::PrefValue::* const member;
+  };
+
+  // Returns a string representation of the PrefType useful for logging.
+  static std::string GetTypeName(PrefType type);
+
+  // Checks that the |key| is either not present or has the given |type|.
+  void CheckKeyType(const std::string& key, PrefType type) const;
+
+  // Helper function to set a value of the passed |key|. It sets the type based
+  // on the template parameter T.
+  template<typename T>
+  void SetValue(const std::string& key, const T& value);
+
+  // Helper function to get a value from the map checking for invalid calls.
+  // The function fails the test if you attempt to read a value  defined as a
+  // different type. Returns whether the get succeeded.
+  template<typename T>
+  bool GetValue(const std::string& key, T* value) const;
+
+  // Container for all the key/value pairs.
+  std::map<std::string, PrefTypeValue> values_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakePrefs);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_PREFS_H_
diff --git a/fake_system_state.cc b/fake_system_state.cc
new file mode 100644
index 0000000..8747a59
--- /dev/null
+++ b/fake_system_state.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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/fake_system_state.h"
+
+namespace chromeos_update_engine {
+
+// Mock the SystemStateInterface so that we could lie that
+// OOBE is completed even when there's no such marker file, etc.
+FakeSystemState::FakeSystemState()
+  : mock_connection_manager_(this),
+    mock_update_attempter_(this, &dbus_),
+    mock_request_params_(this),
+    fake_update_manager_(&fake_clock_),
+    clock_(&fake_clock_),
+    connection_manager_(&mock_connection_manager_),
+    hardware_(&fake_hardware_),
+    metrics_lib_(&mock_metrics_lib_),
+    prefs_(&mock_prefs_),
+    powerwash_safe_prefs_(&mock_powerwash_safe_prefs_),
+    payload_state_(&mock_payload_state_),
+    update_attempter_(&mock_update_attempter_),
+    request_params_(&mock_request_params_),
+    p2p_manager_(&mock_p2p_manager_),
+    update_manager_(&fake_update_manager_),
+    device_policy_(nullptr),
+    fake_system_rebooted_(false) {
+  mock_payload_state_.Initialize(this);
+  mock_update_attempter_.Init();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/fake_system_state.h b/fake_system_state.h
new file mode 100644
index 0000000..84c37fc
--- /dev/null
+++ b/fake_system_state.h
@@ -0,0 +1,239 @@
+// Copyright (c) 2012 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_FAKE_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
+
+#include <base/logging.h>
+#include <gmock/gmock.h>
+#include <policy/mock_device_policy.h>
+
+#include "metrics/metrics_library_mock.h"
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/mock_omaha_request_params.h"
+#include "update_engine/mock_p2p_manager.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/mock_update_attempter.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+
+namespace chromeos_update_engine {
+
+// Mock the SystemStateInterface so that we could lie that
+// OOBE is completed even when there's no such marker file, etc.
+class FakeSystemState : public SystemState {
+ public:
+  FakeSystemState();
+
+  // Base class overrides. All getters return the current implementation of
+  // various members, either the default (fake/mock) or the one set to override
+  // it by client code.
+
+  inline ClockInterface* clock() override { return clock_; }
+
+  inline void set_device_policy(
+      const policy::DevicePolicy* device_policy) override {
+    device_policy_ = device_policy;
+  }
+
+  inline const policy::DevicePolicy* device_policy() override {
+    return device_policy_;
+  }
+
+  inline ConnectionManager* connection_manager() override {
+    return connection_manager_;
+  }
+
+  inline HardwareInterface* hardware() override { return hardware_; }
+
+  inline MetricsLibraryInterface* metrics_lib() override {
+    return metrics_lib_;
+  }
+
+  inline PrefsInterface* prefs() override { return prefs_; }
+
+  inline PrefsInterface* powerwash_safe_prefs() override {
+    return powerwash_safe_prefs_;
+  }
+
+  inline PayloadStateInterface* payload_state() override {
+    return payload_state_;
+  }
+
+  inline UpdateAttempter* update_attempter() override {
+    return update_attempter_;
+  }
+
+  inline OmahaRequestParams* request_params() override {
+    return request_params_;
+  }
+
+  inline P2PManager* p2p_manager() override { return p2p_manager_; }
+
+  inline chromeos_update_manager::UpdateManager* update_manager() override {
+    return update_manager_;
+  }
+
+  inline bool system_rebooted() override { return fake_system_rebooted_; }
+
+  // Setters for the various members, can be used for overriding the default
+  // implementations. For convenience, setting to a null pointer will restore
+  // the default implementation.
+
+  inline void set_clock(ClockInterface* clock) {
+    clock_ = clock ? clock : &fake_clock_;
+  }
+
+  inline void set_connection_manager(ConnectionManager* connection_manager) {
+    connection_manager_ = (connection_manager ? connection_manager :
+                           &mock_connection_manager_);
+  }
+
+  inline void set_hardware(HardwareInterface* hardware) {
+    hardware_ = hardware ? hardware : &fake_hardware_;
+  }
+
+  inline void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+    metrics_lib_ = metrics_lib ? metrics_lib : &mock_metrics_lib_;
+  }
+
+  inline void set_prefs(PrefsInterface* prefs) {
+    prefs_ = prefs ? prefs : &mock_prefs_;
+  }
+
+  inline void set_powerwash_safe_prefs(PrefsInterface* powerwash_safe_prefs) {
+    powerwash_safe_prefs_ = (powerwash_safe_prefs ? powerwash_safe_prefs :
+                             &mock_powerwash_safe_prefs_);
+  }
+
+  inline void set_payload_state(PayloadStateInterface *payload_state) {
+    payload_state_ = payload_state ? payload_state : &mock_payload_state_;
+  }
+
+  inline void set_update_attempter(UpdateAttempter* update_attempter) {
+    update_attempter_ = (update_attempter ? update_attempter :
+                         &mock_update_attempter_);
+  }
+
+  inline void set_request_params(OmahaRequestParams* request_params) {
+    request_params_ = (request_params ? request_params :
+                       &mock_request_params_);
+  }
+
+  inline void set_p2p_manager(P2PManager *p2p_manager) {
+    p2p_manager_ = p2p_manager ? p2p_manager : &mock_p2p_manager_;
+  }
+
+  inline void set_update_manager(
+      chromeos_update_manager::UpdateManager *update_manager) {
+    update_manager_ = update_manager ? update_manager : &fake_update_manager_;
+  }
+
+  inline void set_system_rebooted(bool system_rebooted) {
+    fake_system_rebooted_ = system_rebooted;
+  }
+
+  // Getters for the built-in default implementations. These return the actual
+  // concrete type of each implementation. For additional safety, they will fail
+  // whenever the requested default was overridden by a different
+  // implementation.
+
+  inline FakeClock* fake_clock() {
+    CHECK(clock_ == &fake_clock_);
+    return &fake_clock_;
+  }
+
+  inline testing::NiceMock<MockConnectionManager>* mock_connection_manager() {
+    CHECK(connection_manager_ == &mock_connection_manager_);
+    return &mock_connection_manager_;
+  }
+
+  inline FakeHardware* fake_hardware() {
+    CHECK(hardware_ == &fake_hardware_);
+    return &fake_hardware_;
+  }
+
+  inline testing::NiceMock<MetricsLibraryMock>* mock_metrics_lib() {
+    CHECK(metrics_lib_ == &mock_metrics_lib_);
+    return &mock_metrics_lib_;
+  }
+
+  inline testing::NiceMock<MockPrefs> *mock_prefs() {
+    CHECK(prefs_ == &mock_prefs_);
+    return &mock_prefs_;
+  }
+
+  inline testing::NiceMock<MockPrefs> *mock_powerwash_safe_prefs() {
+    CHECK(powerwash_safe_prefs_ == &mock_powerwash_safe_prefs_);
+    return &mock_powerwash_safe_prefs_;
+  }
+
+  inline testing::NiceMock<MockPayloadState>* mock_payload_state() {
+    CHECK(payload_state_ == &mock_payload_state_);
+    return &mock_payload_state_;
+  }
+
+  inline testing::NiceMock<MockUpdateAttempter>* mock_update_attempter() {
+    CHECK(update_attempter_ == &mock_update_attempter_);
+    return &mock_update_attempter_;
+  }
+
+  inline testing::NiceMock<MockOmahaRequestParams>* mock_request_params() {
+    CHECK(request_params_ == &mock_request_params_);
+    return &mock_request_params_;
+  }
+
+  inline testing::NiceMock<MockP2PManager>* mock_p2p_manager() {
+    CHECK(p2p_manager_ == &mock_p2p_manager_);
+    return &mock_p2p_manager_;
+  }
+
+  inline chromeos_update_manager::FakeUpdateManager* fake_update_manager() {
+    CHECK(update_manager_ == &fake_update_manager_);
+    return &fake_update_manager_;
+  }
+
+ private:
+  // Default mock/fake implementations (owned).
+  FakeClock fake_clock_;
+  testing::NiceMock<MockConnectionManager> mock_connection_manager_;
+  FakeHardware fake_hardware_;
+  testing::NiceMock<MetricsLibraryMock> mock_metrics_lib_;
+  testing::NiceMock<MockPrefs> mock_prefs_;
+  testing::NiceMock<MockPrefs> mock_powerwash_safe_prefs_;
+  testing::NiceMock<MockPayloadState> mock_payload_state_;
+  testing::NiceMock<MockUpdateAttempter> mock_update_attempter_;
+  testing::NiceMock<MockOmahaRequestParams> mock_request_params_;
+  testing::NiceMock<MockP2PManager> mock_p2p_manager_;
+  chromeos_update_manager::FakeUpdateManager fake_update_manager_;
+
+  // Pointers to objects that client code can override. They are initialized to
+  // the default implementations above.
+  ClockInterface* clock_;
+  ConnectionManager* connection_manager_;
+  HardwareInterface* hardware_;
+  MetricsLibraryInterface* metrics_lib_;
+  PrefsInterface* prefs_;
+  PrefsInterface* powerwash_safe_prefs_;
+  PayloadStateInterface* payload_state_;
+  UpdateAttempter* update_attempter_;
+  OmahaRequestParams* request_params_;
+  P2PManager* p2p_manager_;
+  chromeos_update_manager::UpdateManager* update_manager_;
+
+  // Other object pointers (not preinitialized).
+  const policy::DevicePolicy* device_policy_;
+
+  // Other data members.
+  MockDBusWrapper dbus_;
+  bool fake_system_rebooted_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
diff --git a/file_descriptor.cc b/file_descriptor.cc
new file mode 100644
index 0000000..9907a8c
--- /dev/null
+++ b/file_descriptor.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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/file_descriptor.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <base/posix/eintr_wrapper.h>
+
+namespace chromeos_update_engine {
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  CHECK_EQ(fd_, -1);
+  return ((fd_ = HANDLE_EINTR(open(path, flags, mode))) >= 0);
+}
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags) {
+  CHECK_EQ(fd_, -1);
+  return ((fd_ = HANDLE_EINTR(open(path, flags))) >= 0);
+}
+
+ssize_t EintrSafeFileDescriptor::Read(void* buf, size_t count) {
+  CHECK_GE(fd_, 0);
+  return HANDLE_EINTR(read(fd_, buf, count));
+}
+
+ssize_t EintrSafeFileDescriptor::Write(const void* buf, size_t count) {
+  CHECK_GE(fd_, 0);
+
+  // Attempt repeated writes, as long as some progress is being made.
+  char* char_buf = const_cast<char*>(reinterpret_cast<const char*>(buf));
+  ssize_t written = 0;
+  while (count > 0) {
+    ssize_t ret = HANDLE_EINTR(write(fd_, char_buf, count));
+
+    // Fail on either an error or no progress.
+    if (ret <= 0)
+      return (written ? written : ret);
+    written += ret;
+    count -= ret;
+    char_buf += ret;
+  }
+  return written;
+}
+
+off64_t EintrSafeFileDescriptor::Seek(off64_t offset, int whence) {
+  CHECK_GE(fd_, 0);
+  return lseek64(fd_, offset, whence);
+}
+
+bool EintrSafeFileDescriptor::Close() {
+  CHECK_GE(fd_, 0);
+  if (IGNORE_EINTR(close(fd_)))
+    return false;
+  Reset();
+  return true;
+}
+
+void EintrSafeFileDescriptor::Reset() {
+  fd_ = -1;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/file_descriptor.h b/file_descriptor.h
new file mode 100644
index 0000000..f2e5caf
--- /dev/null
+++ b/file_descriptor.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 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_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_FILE_DESCRIPTOR_H_
+
+#include <errno.h>
+#include <memory>
+#include <sys/types.h>
+
+#include <base/logging.h>
+
+// Abstraction for managing opening, reading, writing and closing of file
+// descriptors. This includes an abstract class and one standard implementation
+// based on POSIX system calls.
+//
+// TODO(garnold) this class is modeled after (and augments the functionality of)
+// the FileWriter class; ultimately, the latter should be replaced by the former
+// throughout the codebase.  A few deviations from the original FileWriter:
+//
+// * Providing two flavors of Open()
+//
+// * A FileDescriptor is reusable and can be used to read/write multiple files
+//   as long as open/close preconditions are respected.
+//
+// * Write() returns the number of bytes written: this appears to be more useful
+//   for clients, who may wish to retry or otherwise do something useful with
+//   the remaining data that was not written.
+//
+// * Provides a Reset() method, which will force to abandon a currently open
+//   file descriptor and allow opening another file, without necessarily
+//   properly closing the old one. This may be useful in cases where a "closer"
+//   class does not care whether Close() was successful, but may need to reuse
+//   the same file descriptor again.
+
+namespace chromeos_update_engine {
+
+class FileDescriptor;
+using FileDescriptorPtr = std::shared_ptr<FileDescriptor>;
+
+// An abstract class defining the file descriptor API.
+class FileDescriptor {
+ public:
+  FileDescriptor() {}
+  virtual ~FileDescriptor() {}
+
+  // Opens a file descriptor. The descriptor must be in the closed state prior
+  // to this call. Returns true on success, false otherwise. Specific
+  // implementations may set errno accordingly.
+  virtual bool Open(const char* path, int flags, mode_t mode) = 0;
+  virtual bool Open(const char* path, int flags) = 0;
+
+  // Reads from a file descriptor up to a given count. The descriptor must be
+  // open prior to this call. Returns the number of bytes read, or -1 on error.
+  // Specific implementations may set errno accordingly.
+  virtual ssize_t Read(void* buf, size_t count) = 0;
+
+  // Writes to a file descriptor. The descriptor must be open prior to this
+  // call. Returns the number of bytes written, or -1 if an error occurred and
+  // no bytes were written. Specific implementations may set errno accordingly.
+  virtual ssize_t Write(const void* buf, size_t count) = 0;
+
+  // Seeks to an offset. Returns the resulting offset location as measured in
+  // bytes from the beginning. On error, return -1. Specific implementations
+  // may set errno accordingly.
+  virtual off64_t Seek(off64_t offset, int whence) = 0;
+
+  // Closes a file descriptor. The descriptor must be open prior to this call.
+  // Returns true on success, false otherwise. Specific implementations may set
+  // errno accordingly.
+  virtual bool Close() = 0;
+
+  // Resets the file descriptor, abandoning a currently open file and returning
+  // the descriptor to the closed state.
+  virtual void Reset() = 0;
+
+  // Indicates whether or not an implementation sets meaningful errno.
+  virtual bool IsSettingErrno() = 0;
+
+  // Indicates whether the descriptor is currently open.
+  virtual bool IsOpen() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileDescriptor);
+};
+
+// A simple EINTR-immune wrapper implementation around standard system calls.
+class EintrSafeFileDescriptor : public FileDescriptor {
+ public:
+  EintrSafeFileDescriptor() : fd_(-1) {}
+
+  // Interface methods.
+  bool Open(const char* path, int flags, mode_t mode) override;
+  bool Open(const char* path, int flags) override;
+  ssize_t Read(void* buf, size_t count) override;
+  ssize_t Write(const void* buf, size_t count) override;
+  off64_t Seek(off64_t offset, int whence) override;
+  bool Close() override;
+  void Reset() override;
+  bool IsSettingErrno() override {
+    return true;
+  }
+  bool IsOpen() override {
+    return (fd_ >= 0);
+  }
+
+ protected:
+  int fd_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FILE_DESCRIPTOR_H_
diff --git a/file_writer.cc b/file_writer.cc
new file mode 100644
index 0000000..8f8e989
--- /dev/null
+++ b/file_writer.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2009 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/file_writer.h"
+
+#include <errno.h>
+
+namespace chromeos_update_engine {
+
+int DirectFileWriter::Open(const char* path, int flags, mode_t mode) {
+  CHECK_EQ(fd_, -1);
+  fd_ = open(path, flags, mode);
+  if (fd_ < 0)
+    return -errno;
+  return 0;
+}
+
+bool DirectFileWriter::Write(const void* bytes, size_t count) {
+  CHECK_GE(fd_, 0);
+  const char* char_bytes = reinterpret_cast<const char*>(bytes);
+
+  size_t bytes_written = 0;
+  while (bytes_written < count) {
+    ssize_t rc = write(fd_, char_bytes + bytes_written,
+                       count - bytes_written);
+    if (rc < 0)
+      return false;
+    bytes_written += rc;
+  }
+  CHECK_EQ(bytes_written, count);
+  return bytes_written == count;
+}
+
+int DirectFileWriter::Close() {
+  CHECK_GE(fd_, 0);
+  int rc = close(fd_);
+
+  // This can be any negative number that's not -1. This way, this FileWriter
+  // won't be used again for another file.
+  fd_ = -2;
+
+  if (rc < 0)
+    return -errno;
+  return rc;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/file_writer.h b/file_writer.h
new file mode 100644
index 0000000..f800643
--- /dev/null
+++ b/file_writer.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2009 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_FILE_WRITER_H_
+#define UPDATE_ENGINE_FILE_WRITER_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+#include "update_engine/error_code.h"
+#include "update_engine/utils.h"
+
+// FileWriter is a class that is used to (synchronously, for now) write to
+// a file. This file is a thin wrapper around open/write/close system calls,
+// but provides and interface that can be customized by subclasses that wish
+// to filter the data.
+
+namespace chromeos_update_engine {
+
+class FileWriter {
+ public:
+  FileWriter() {}
+  virtual ~FileWriter() {}
+
+  // Wrapper around open. Returns 0 on success or -errno on error.
+  virtual int Open(const char* path, int flags, mode_t mode) = 0;
+
+  // Wrapper around write. Returns true if all requested bytes
+  // were written, or false on any error, regardless of progress.
+  virtual bool Write(const void* bytes, size_t count) = 0;
+
+  // Same as the Write method above but returns a detailed |error| code
+  // in addition if the returned value is false. By default this method
+  // returns kActionExitDownloadWriteError as the error code, but subclasses
+  // can override if they wish to return more specific error codes.
+  virtual bool Write(const void* bytes,
+                     size_t count,
+                     ErrorCode* error) {
+     *error = ErrorCode::kDownloadWriteError;
+     return Write(bytes, count);
+  }
+
+  // Wrapper around close. Returns 0 on success or -errno on error.
+  virtual int Close() = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileWriter);
+};
+
+// Direct file writer is probably the simplest FileWriter implementation.
+// It calls the system calls directly.
+
+class DirectFileWriter : public FileWriter {
+ public:
+  DirectFileWriter() : fd_(-1) {}
+
+  int Open(const char* path, int flags, mode_t mode) override;
+  bool Write(const void* bytes, size_t count) override;
+  int Close() override;
+
+  int fd() const { return fd_; }
+
+ private:
+  int fd_;
+
+  DISALLOW_COPY_AND_ASSIGN(DirectFileWriter);
+};
+
+class ScopedFileWriterCloser {
+ public:
+  explicit ScopedFileWriterCloser(FileWriter* writer) : writer_(writer) {}
+  ~ScopedFileWriterCloser() {
+    int err = writer_->Close();
+    if (err)
+      LOG(ERROR) << "FileWriter::Close failed: "
+                 << utils::ErrnoNumberAsString(-err);
+  }
+ private:
+  FileWriter* writer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedFileWriterCloser);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FILE_WRITER_H_
diff --git a/file_writer_unittest.cc b/file_writer_unittest.cc
new file mode 100644
index 0000000..5bdea3c
--- /dev/null
+++ b/file_writer_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2009 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/file_writer.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FileWriterTest : public ::testing::Test { };
+
+TEST(FileWriterTest, SimpleTest) {
+  // Create a uniquely named file for testing.
+  string path;
+  ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+  ScopedPathUnlinker path_unlinker(path);
+
+  DirectFileWriter file_writer;
+  EXPECT_EQ(0, file_writer.Open(path.c_str(),
+                                O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY,
+                                0644));
+  EXPECT_TRUE(file_writer.Write("test", 4));
+  chromeos::Blob actual_data;
+  EXPECT_TRUE(utils::ReadFile(path, &actual_data));
+
+  EXPECT_FALSE(memcmp("test", actual_data.data(), actual_data.size()));
+  EXPECT_EQ(0, file_writer.Close());
+}
+
+TEST(FileWriterTest, ErrorTest) {
+  DirectFileWriter file_writer;
+  const string path("/tmp/ENOENT/FileWriterTest");
+  EXPECT_EQ(-ENOENT, file_writer.Open(path.c_str(),
+                                      O_CREAT | O_LARGEFILE | O_TRUNC, 0644));
+}
+
+TEST(FileWriterTest, WriteErrorTest) {
+  // Create a uniquely named file for testing.
+  string path;
+  ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+  ScopedPathUnlinker path_unlinker(path);
+
+  DirectFileWriter file_writer;
+  EXPECT_EQ(0, file_writer.Open(path.c_str(),
+                                O_CREAT | O_LARGEFILE | O_TRUNC | O_RDONLY,
+                                0644));
+  EXPECT_FALSE(file_writer.Write("x", 1));
+  EXPECT_EQ(0, file_writer.Close());
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/filesystem_verifier_action.cc b/filesystem_verifier_action.cc
new file mode 100644
index 0000000..70156d9
--- /dev/null
+++ b/filesystem_verifier_action.cc
@@ -0,0 +1,244 @@
+// Copyright (c) 2012 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/filesystem_verifier_action.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+
+#include "update_engine/hardware_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+const off_t kReadFileBufferSize = 128 * 1024;
+}  // namespace
+
+FilesystemVerifierAction::FilesystemVerifierAction(
+    SystemState* system_state,
+    PartitionType partition_type)
+    : partition_type_(partition_type),
+      remaining_size_(kint64max),
+      system_state_(system_state) {}
+
+void FilesystemVerifierAction::PerformAction() {
+  // Will tell the ActionProcessor we've failed if we return.
+  ScopedActionCompleter abort_action_completer(processor_, this);
+
+  if (!HasInputObject()) {
+    LOG(ERROR) << "FilesystemVerifierAction missing input object.";
+    return;
+  }
+  install_plan_ = GetInputObject();
+
+  if (partition_type_ == PartitionType::kKernel) {
+    LOG(INFO) << "verifying kernel, marking as unbootable";
+    if (!system_state_->hardware()->MarkKernelUnbootable(
+        install_plan_.kernel_install_path)) {
+      PLOG(ERROR) << "Unable to clear kernel GPT boot flags: " <<
+          install_plan_.kernel_install_path;
+    }
+  }
+
+  if (install_plan_.is_full_update &&
+      (partition_type_ == PartitionType::kSourceRootfs ||
+       partition_type_ == PartitionType::kSourceKernel)) {
+    // No hash verification needed. Done!
+    LOG(INFO) << "filesystem verifying skipped on full update.";
+    if (HasOutputPipe())
+      SetOutputObject(install_plan_);
+    abort_action_completer.set_code(ErrorCode::kSuccess);
+    return;
+  }
+
+  string target_path;
+  switch (partition_type_) {
+    case PartitionType::kRootfs:
+      target_path = install_plan_.install_path;
+      if (target_path.empty()) {
+        utils::GetInstallDev(system_state_->hardware()->BootDevice(),
+                             &target_path);
+      }
+      break;
+    case PartitionType::kKernel:
+      target_path = install_plan_.kernel_install_path;
+      if (target_path.empty()) {
+        string rootfs_path;
+        utils::GetInstallDev(system_state_->hardware()->BootDevice(),
+                             &rootfs_path);
+        target_path = utils::KernelDeviceOfBootDevice(rootfs_path);
+      }
+      break;
+    case PartitionType::kSourceRootfs:
+      target_path = install_plan_.source_path.empty() ?
+                    system_state_->hardware()->BootDevice() :
+                    install_plan_.source_path;
+      break;
+    case PartitionType::kSourceKernel:
+      target_path = install_plan_.kernel_source_path.empty() ?
+                    utils::KernelDeviceOfBootDevice(
+                        system_state_->hardware()->BootDevice()) :
+                    install_plan_.kernel_source_path;
+      break;
+  }
+
+  src_fd_ = HANDLE_EINTR(open(target_path.c_str(), O_RDONLY));
+  if (src_fd_ < 0) {
+    PLOG(ERROR) << "Unable to open " << target_path << " for reading";
+    return;
+  }
+
+  DetermineFilesystemSize(src_fd_);
+  buffer_.resize(kReadFileBufferSize);
+
+  // Start the first read.
+  read_task_ = MessageLoop::current()->WatchFileDescriptor(
+      FROM_HERE,
+      src_fd_,
+      MessageLoop::WatchMode::kWatchRead,
+      true,  // persistent
+      base::Bind(&FilesystemVerifierAction::OnReadReadyCallback,
+                 base::Unretained(this)));
+
+  abort_action_completer.set_should_complete(false);
+}
+
+void FilesystemVerifierAction::TerminateProcessing() {
+  cancelled_ = true;
+  Cleanup(ErrorCode::kSuccess);  // error code is ignored if canceled_ is true.
+}
+
+bool FilesystemVerifierAction::IsCleanupPending() const {
+  return (src_fd_ != -1);
+}
+
+void FilesystemVerifierAction::Cleanup(ErrorCode code) {
+  MessageLoop::current()->CancelTask(read_task_);
+  read_task_ = MessageLoop::kTaskIdNull;
+
+  if (src_fd_ != -1) {
+    if (IGNORE_EINTR(close(src_fd_)) != 0) {
+      PLOG(ERROR) << "Error closing fd " << src_fd_;
+    }
+    src_fd_ = -1;
+  }
+  // This memory is not used anymore.
+  buffer_.clear();
+
+  if (cancelled_)
+    return;
+  if (code == ErrorCode::kSuccess && HasOutputPipe())
+    SetOutputObject(install_plan_);
+  processor_->ActionComplete(this, code);
+}
+
+void FilesystemVerifierAction::OnReadReadyCallback() {
+  size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
+                                  remaining_size_);
+
+  ssize_t bytes_read = 0;
+  if (bytes_to_read) {
+    bytes_read = HANDLE_EINTR(read(src_fd_, buffer_.data(), bytes_to_read));
+  }
+  if (bytes_read < 0) {
+    PLOG(ERROR) << "Read failed";
+    failed_ = true;
+  } else if (bytes_read == 0) {
+    read_done_ = true;
+  } else {
+    remaining_size_ -= bytes_read;
+  }
+
+  if (bytes_read > 0) {
+    CHECK(!read_done_);
+    if (!hasher_.Update(buffer_.data(), bytes_read)) {
+      LOG(ERROR) << "Unable to update the hash.";
+      failed_ = true;
+    }
+  }
+
+  CheckTerminationConditions();
+}
+
+void FilesystemVerifierAction::CheckTerminationConditions() {
+  if (failed_ || cancelled_) {
+    Cleanup(ErrorCode::kError);
+    return;
+  }
+
+  if (read_done_) {
+    // We're done!
+    ErrorCode code = ErrorCode::kSuccess;
+    if (hasher_.Finalize()) {
+      LOG(INFO) << "Hash: " << hasher_.hash();
+      switch (partition_type_) {
+        case PartitionType::kRootfs:
+          if (install_plan_.rootfs_hash != hasher_.raw_hash()) {
+            code = ErrorCode::kNewRootfsVerificationError;
+            LOG(ERROR) << "New rootfs verification failed.";
+          }
+          break;
+        case PartitionType::kKernel:
+          if (install_plan_.kernel_hash != hasher_.raw_hash()) {
+            code = ErrorCode::kNewKernelVerificationError;
+            LOG(ERROR) << "New kernel verification failed.";
+          }
+          break;
+        case PartitionType::kSourceRootfs:
+          install_plan_.source_rootfs_hash = hasher_.raw_hash();
+          break;
+        case PartitionType::kSourceKernel:
+          install_plan_.source_kernel_hash = hasher_.raw_hash();
+          break;
+      }
+    } else {
+      LOG(ERROR) << "Unable to finalize the hash.";
+      code = ErrorCode::kError;
+    }
+    Cleanup(code);
+  }
+}
+
+void FilesystemVerifierAction::DetermineFilesystemSize(int fd) {
+  switch (partition_type_) {
+    case PartitionType::kRootfs:
+      remaining_size_ = install_plan_.rootfs_size;
+      LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
+      break;
+    case PartitionType::kKernel:
+      remaining_size_ = install_plan_.kernel_size;
+      LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes.";
+      break;
+    case PartitionType::kSourceRootfs:
+      {
+        int block_count = 0, block_size = 0;
+        if (utils::GetFilesystemSizeFromFD(fd, &block_count, &block_size)) {
+          remaining_size_ = static_cast<int64_t>(block_count) * block_size;
+          LOG(INFO) << "Filesystem size: " << remaining_size_ << " bytes ("
+                    << block_count << "x" << block_size << ").";
+        }
+      }
+      break;
+    default:
+      break;
+  }
+  return;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/filesystem_verifier_action.h b/filesystem_verifier_action.h
new file mode 100644
index 0000000..69e4972
--- /dev/null
+++ b/filesystem_verifier_action.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2012 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_FILESYSTEM_VERIFIER_ACTION_H_
+#define UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_hash_calculator.h"
+
+// This action will only do real work if it's a delta update. It will
+// copy the root partition to install partition, and then terminate.
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// The type of filesystem that we are verifying.
+enum class PartitionType {
+  kSourceRootfs,
+  kSourceKernel,
+  kRootfs,
+  kKernel,
+};
+
+class FilesystemVerifierAction : public InstallPlanAction {
+ public:
+  FilesystemVerifierAction(SystemState* system_state,
+                           PartitionType partition_type);
+
+  void PerformAction() override;
+  void TerminateProcessing() override;
+
+  // Used for testing. Return true if Cleanup() has not yet been called due
+  // to a callback upon the completion or cancellation of the verifier action.
+  // A test should wait until IsCleanupPending() returns false before
+  // terminating the glib main loop.
+  bool IsCleanupPending() const;
+
+  // Debugging/logging
+  static std::string StaticType() { return "FilesystemVerifierAction"; }
+  std::string Type() const override { return StaticType(); }
+
+ private:
+  friend class FilesystemVerifierActionTest;
+  FRIEND_TEST(FilesystemVerifierActionTest,
+              RunAsRootDetermineFilesystemSizeTest);
+
+  // Callback from the main loop when there's data to read from the file
+  // descriptor.
+  void OnReadReadyCallback();
+
+  // Based on the state of the read buffers, terminates read process and the
+  // action.
+  void CheckTerminationConditions();
+
+  // Cleans up all the variables we use for async operations and tells the
+  // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
+  // true if TerminateProcessing() was called.
+  void Cleanup(ErrorCode code);
+
+  // Determine, if possible, the source file system size to avoid copying the
+  // whole partition. Currently this supports only the root file system assuming
+  // it's ext3-compatible.
+  void DetermineFilesystemSize(int fd);
+
+  // The type of the partition that we are verifying.
+  PartitionType partition_type_;
+
+  // If non-null, this is the GUnixInputStream object for the opened source
+  // partition.
+  int src_fd_{-1};
+
+  // Buffer for storing data we read.
+  chromeos::Blob buffer_;
+
+  // The task id for the the in-flight async call.
+  chromeos::MessageLoop::TaskId read_task_{chromeos::MessageLoop::kTaskIdNull};
+
+  bool read_done_{false};  // true if reached EOF on the input stream.
+  bool failed_{false};  // true if the action has failed.
+  bool cancelled_{false};  // true if the action has been cancelled.
+
+  // The install plan we're passed in via the input pipe.
+  InstallPlan install_plan_;
+
+  // Calculates the hash of the data.
+  OmahaHashCalculator hasher_;
+
+  // Reads and hashes this many bytes from the head of the input stream. This
+  // field is initialized when the action is started and decremented as more
+  // bytes get read.
+  int64_t remaining_size_;
+
+  // The global context for update_engine.
+  SystemState* system_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FILESYSTEM_VERIFIER_ACTION_H_
diff --git a/filesystem_verifier_action_unittest.cc b/filesystem_verifier_action_unittest.cc
new file mode 100644
index 0000000..da0fd95
--- /dev/null
+++ b/filesystem_verifier_action_unittest.cc
@@ -0,0 +1,381 @@
+// Copyright (c) 2012 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/filesystem_verifier_action.h"
+
+#include <fcntl.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_hardware.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FilesystemVerifierActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+  // Returns true iff test has completed successfully.
+  bool DoTest(bool terminate_early,
+              bool hash_fail,
+              PartitionType partition_type);
+
+  chromeos::GlibMessageLoop loop_;
+  FakeSystemState fake_system_state_;
+};
+
+class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
+ public:
+  explicit FilesystemVerifierActionTestDelegate(
+      FilesystemVerifierAction* action)
+      : action_(action), ran_(false), code_(ErrorCode::kError) {}
+  void ExitMainLoop() {
+    // We need to wait for the Action to call Cleanup.
+    if (action_->IsCleanupPending()) {
+      LOG(INFO) << "Waiting for Cleanup() to be called.";
+      MessageLoop::current()->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(&FilesystemVerifierActionTestDelegate::ExitMainLoop,
+                     base::Unretained(this)),
+          base::TimeDelta::FromMilliseconds(100));
+    } else {
+      MessageLoop::current()->BreakLoop();
+    }
+  }
+  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+    ExitMainLoop();
+  }
+  void ProcessingStopped(const ActionProcessor* processor) {
+    ExitMainLoop();
+  }
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == FilesystemVerifierAction::StaticType()) {
+      ran_ = true;
+      code_ = code;
+    }
+  }
+  bool ran() const { return ran_; }
+  ErrorCode code() const { return code_; }
+
+ private:
+  FilesystemVerifierAction* action_;
+  bool ran_;
+  ErrorCode code_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+                             FilesystemVerifierAction* filesystem_copier_action,
+                             bool terminate_early) {
+  processor->StartProcessing();
+  if (terminate_early) {
+    EXPECT_NE(nullptr, filesystem_copier_action);
+    processor->StopProcessing();
+  }
+}
+
+// TODO(garnold) Temporarily disabling this test, see chromium-os:31082 for
+// details; still trying to track down the root cause for these rare write
+// failures and whether or not they are due to the test setup or an inherent
+// issue with the chroot environment, library versions we use, etc.
+TEST_F(FilesystemVerifierActionTest, DISABLED_RunAsRootSimpleTest) {
+  ASSERT_EQ(0, getuid());
+  bool test = DoTest(false, false, PartitionType::kKernel);
+  EXPECT_TRUE(test);
+  if (!test)
+    return;
+  test = DoTest(false, false, PartitionType::kRootfs);
+  EXPECT_TRUE(test);
+}
+
+bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
+                                          bool hash_fail,
+                                          PartitionType partition_type) {
+  // We need MockHardware to verify MarkUnbootable calls, but don't want
+  // warnings about other usages.
+  testing::NiceMock<MockHardware> mock_hardware;
+  fake_system_state_.set_hardware(&mock_hardware);
+
+  string a_loop_file;
+
+  if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
+    ADD_FAILURE();
+    return false;
+  }
+  ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+
+  // Make random data for a.
+  const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
+  chromeos::Blob a_loop_data(kLoopFileSize);
+  test_utils::FillWithData(&a_loop_data);
+
+
+  // Write data to disk
+  if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
+    ADD_FAILURE();
+    return false;
+  }
+
+  // Attach loop devices to the files
+  string a_dev;
+  test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(a_loop_file, &a_dev);
+  if (!(a_dev_releaser.is_bound())) {
+    ADD_FAILURE();
+    return false;
+  }
+
+  LOG(INFO) << "verifying: "  << a_loop_file << " (" << a_dev << ")";
+
+  bool success = true;
+
+  // Set up the action objects
+  InstallPlan install_plan;
+  switch (partition_type) {
+    case PartitionType::kRootfs:
+      install_plan.rootfs_size = kLoopFileSize - (hash_fail ? 1 : 0);
+      install_plan.install_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.rootfs_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+    case PartitionType::kKernel:
+      install_plan.kernel_size = kLoopFileSize - (hash_fail ? 1 : 0);
+      install_plan.kernel_install_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.kernel_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+    case PartitionType::kSourceRootfs:
+      install_plan.source_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.source_rootfs_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+    case PartitionType::kSourceKernel:
+      install_plan.kernel_source_path = a_dev;
+      if (!OmahaHashCalculator::RawHashOfData(
+          a_loop_data, &install_plan.source_kernel_hash)) {
+        ADD_FAILURE();
+        success = false;
+      }
+      break;
+  }
+
+  EXPECT_CALL(mock_hardware,
+              MarkKernelUnbootable(a_dev)).Times(
+                  partition_type == PartitionType::kKernel ? 1 : 0);
+
+  ActionProcessor processor;
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  FilesystemVerifierAction copier_action(&fake_system_state_, partition_type);
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&feeder_action, &copier_action);
+  BondActions(&copier_action, &collector_action);
+
+  FilesystemVerifierActionTestDelegate delegate(&copier_action);
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+
+  feeder_action.set_obj(install_plan);
+
+  loop_.PostTask(FROM_HERE, base::Bind(&StartProcessorInRunLoop,
+                                       &processor,
+                                       &copier_action,
+                                       terminate_early));
+  loop_.Run();
+
+  if (!terminate_early) {
+    bool is_delegate_ran = delegate.ran();
+    EXPECT_TRUE(is_delegate_ran);
+    success = success && is_delegate_ran;
+  } else {
+    EXPECT_EQ(ErrorCode::kError, delegate.code());
+    return (ErrorCode::kError == delegate.code());
+  }
+  if (hash_fail) {
+    ErrorCode expected_exit_code =
+        ((partition_type == PartitionType::kKernel ||
+          partition_type == PartitionType::kSourceKernel) ?
+         ErrorCode::kNewKernelVerificationError :
+         ErrorCode::kNewRootfsVerificationError);
+    EXPECT_EQ(expected_exit_code, delegate.code());
+    return (expected_exit_code == delegate.code());
+  }
+  EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
+
+  // Make sure everything in the out_image is there
+  chromeos::Blob a_out;
+  if (!utils::ReadFile(a_dev, &a_out)) {
+    ADD_FAILURE();
+    return false;
+  }
+  const bool is_a_file_reading_eq =
+      test_utils::ExpectVectorsEq(a_loop_data, a_out);
+  EXPECT_TRUE(is_a_file_reading_eq);
+  success = success && is_a_file_reading_eq;
+
+  bool is_install_plan_eq = (collector_action.object() == install_plan);
+  EXPECT_TRUE(is_install_plan_eq);
+  success = success && is_install_plan_eq;
+
+  LOG(INFO) << "Verifying bootable flag on: " << a_dev;
+  bool bootable;
+  EXPECT_TRUE(mock_hardware.fake().IsKernelBootable(a_dev, &bootable));
+  // We should always mark a partition as unbootable if it's a kernel
+  // partition, but never if it's anything else.
+  EXPECT_EQ(bootable, (partition_type != PartitionType::kKernel));
+
+  return success;
+}
+
+class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
+ public:
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == FilesystemVerifierAction::StaticType()) {
+      ran_ = true;
+      code_ = code;
+    }
+  }
+  bool ran_;
+  ErrorCode code_;
+};
+
+TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
+  ActionProcessor processor;
+  FilesystemVerifierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  FilesystemVerifierAction copier_action(&fake_system_state_,
+                                         PartitionType::kRootfs);
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&copier_action, &collector_action);
+
+  processor.EnqueueAction(&copier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
+  ActionProcessor processor;
+  FilesystemVerifierActionTest2Delegate delegate;
+
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan install_plan(false,
+                           false,
+                           "",
+                           0,
+                           "",
+                           0,
+                           "",
+                           "/no/such/file",
+                           "/no/such/file",
+                           "/no/such/file",
+                           "/no/such/file",
+                           "");
+  feeder_action.set_obj(install_plan);
+  FilesystemVerifierAction verifier_action(&fake_system_state_,
+                                           PartitionType::kRootfs);
+  ObjectCollectorAction<InstallPlan> collector_action;
+
+  BondActions(&verifier_action, &collector_action);
+
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&verifier_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_FALSE(processor.IsRunning());
+  EXPECT_TRUE(delegate.ran_);
+  EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
+  ASSERT_EQ(0, getuid());
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kRootfs));
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kKernel));
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceRootfs));
+  EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
+  ASSERT_EQ(0, getuid());
+  EXPECT_TRUE(DoTest(false, true, PartitionType::kRootfs));
+  EXPECT_TRUE(DoTest(false, true, PartitionType::kKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
+  ASSERT_EQ(0, getuid());
+  EXPECT_TRUE(DoTest(true, false, PartitionType::kKernel));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootDetermineFilesystemSizeTest) {
+  string img;
+  EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+  ScopedPathUnlinker img_unlinker(img);
+  test_utils::CreateExtImageAtPath(img, nullptr);
+  // Extend the "partition" holding the file system from 10MiB to 20MiB.
+  EXPECT_EQ(0, truncate(img.c_str(), 20 * 1024 * 1024));
+
+  for (int i = 0; i < 2; ++i) {
+    PartitionType fs_type =
+        i ? PartitionType::kSourceKernel : PartitionType::kSourceRootfs;
+    FilesystemVerifierAction action(&fake_system_state_, fs_type);
+    EXPECT_EQ(kint64max, action.remaining_size_);
+    {
+      int fd = HANDLE_EINTR(open(img.c_str(), O_RDONLY));
+      EXPECT_GT(fd, 0);
+      ScopedFdCloser fd_closer(&fd);
+      action.DetermineFilesystemSize(fd);
+    }
+    EXPECT_EQ(i ? kint64max : 10 * 1024 * 1024,
+              action.remaining_size_);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/generate_image.gypi b/generate_image.gypi
new file mode 100644
index 0000000..025e925
--- /dev/null
+++ b/generate_image.gypi
@@ -0,0 +1,27 @@
+{
+  'variables': {
+    'out_dir': '<(SHARED_INTERMEDIATE_DIR)/<(image_out_dir)',
+    'generator': 'sample_images/generate_image.sh',
+  },
+  'rules': [
+    {
+      'rule_name': 'generate_image',
+      'extension': 'txt',
+      'inputs': [
+        '<(generator)',
+        '<(RULE_INPUT_PATH)',
+      ],
+      'outputs': [
+        '<(out_dir)/<(RULE_INPUT_ROOT).img',
+      ],
+      'action': [
+        '<(generator)',
+        '<(RULE_INPUT_PATH)',
+        '<(out_dir)',
+      ],
+      'msvs_cygwin_shell': 0,
+      'message': 'Generating image from <(RULE_INPUT_PATH)',
+      'process_outputs_as_sources': 1,
+    },
+  ],
+}
diff --git a/glib_utils.cc b/glib_utils.cc
new file mode 100644
index 0000000..625d8d1
--- /dev/null
+++ b/glib_utils.cc
@@ -0,0 +1,37 @@
+// Copyright 2014 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/glib_utils.h"
+
+#include <base/strings/stringprintf.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+namespace utils {
+
+string GetAndFreeGError(GError** error) {
+  if (!*error) {
+    return "Unknown GLib error.";
+  }
+  string message =
+      base::StringPrintf("GError(%d): %s",
+                         (*error)->code,
+                         (*error)->message ? (*error)->message : "(unknown)");
+  g_error_free(*error);
+  *error = nullptr;
+  return message;
+}
+
+gchar** StringVectorToGStrv(const std::vector<string> &vec_str) {
+  GPtrArray *p = g_ptr_array_new();
+  for (const string& str : vec_str) {
+    g_ptr_array_add(p, g_strdup(str.c_str()));
+  }
+  g_ptr_array_add(p, nullptr);
+  return reinterpret_cast<gchar**>(g_ptr_array_free(p, FALSE));
+}
+
+}  // namespace utils
+}  // namespace chromeos_update_engine
diff --git a/glib_utils.h b/glib_utils.h
new file mode 100644
index 0000000..97020cb
--- /dev/null
+++ b/glib_utils.h
@@ -0,0 +1,46 @@
+// Copyright 2014 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_GLIB_UTILS_H_
+#define UPDATE_ENGINE_GLIB_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <glib.h>
+
+namespace chromeos_update_engine {
+namespace utils {
+
+// Returns the error message, if any, from a GError pointer. Frees the GError
+// object and resets error to null.
+std::string GetAndFreeGError(GError** error);
+
+// Converts a vector of strings to a NUL-terminated array of
+// strings. The resulting array should be freed with g_strfreev()
+// when are you done with it.
+gchar** StringVectorToGStrv(const std::vector<std::string>& vec_str);
+
+// A base::FreeDeleter that frees memory using g_free(). Useful when
+// integrating with GLib since it can be used with std::unique_ptr to
+// automatically free memory when going out of scope.
+struct GLibFreeDeleter {
+  inline void operator()(void* ptr) const {
+    g_free(static_cast<gpointer>(ptr));
+  }
+};
+
+// A base::FreeDeleter that frees memory using g_strfreev(). Useful
+// when integrating with GLib since it can be used with std::unique_ptr to
+// automatically free memory when going out of scope.
+struct GLibStrvFreeDeleter {
+  inline void operator()(void* ptr) const {
+    g_strfreev(static_cast<gchar**>(ptr));
+  }
+};
+
+}  // namespace utils
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_GLIB_UTILS_H_
diff --git a/hardware.cc b/hardware.cc
new file mode 100644
index 0000000..a9f0b25
--- /dev/null
+++ b/hardware.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2013 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/hardware.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <rootdev/rootdev.h>
+#include <vboot/crossystem.h>
+
+extern "C" {
+#include "vboot/vboot_host.h"
+}
+
+#include "update_engine/hwid_override.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+static const char kOOBECompletedMarker[] = "/home/chronos/.oobe_completed";
+
+// The powerwash_count marker file contains the number of times the device was
+// powerwashed. This value is incremented by the clobber-state script when
+// a powerwash is performed.
+static const char kPowerwashCountMarker[] =
+    "/mnt/stateful_partition/unencrypted/preserve/powerwash_count";
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+Hardware::Hardware() {}
+
+Hardware::~Hardware() {}
+
+string Hardware::BootKernelDevice() const {
+  return utils::KernelDeviceOfBootDevice(Hardware::BootDevice());
+}
+
+string Hardware::BootDevice() const {
+  char boot_path[PATH_MAX];
+  // Resolve the boot device path fully, including dereferencing
+  // through dm-verity.
+  int ret = rootdev(boot_path, sizeof(boot_path), true, false);
+
+  if (ret < 0) {
+    LOG(ERROR) << "rootdev failed to find the root device";
+    return "";
+  }
+  LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
+
+  // This local variable is used to construct the return string and is not
+  // passed around after use.
+  return boot_path;
+}
+
+bool Hardware::IsBootDeviceRemovable() const {
+  return utils::IsRemovableDevice(utils::GetDiskName(BootDevice()));
+}
+
+bool Hardware::IsKernelBootable(const string& kernel_device,
+                                bool* bootable) const {
+  CgptAddParams params;
+  memset(&params, '\0', sizeof(params));
+
+  string disk_name;
+  int partition_num = 0;
+
+  if (!utils::SplitPartitionName(kernel_device, &disk_name, &partition_num))
+    return false;
+
+  params.drive_name = const_cast<char *>(disk_name.c_str());
+  params.partition = partition_num;
+
+  int retval = CgptGetPartitionDetails(&params);
+  if (retval != CGPT_OK)
+    return false;
+
+  *bootable = params.successful || (params.tries > 0);
+  return true;
+}
+
+vector<string> Hardware::GetKernelDevices() const {
+  LOG(INFO) << "GetAllKernelDevices";
+
+  string disk_name = utils::GetDiskName(Hardware::BootKernelDevice());
+  if (disk_name.empty()) {
+    LOG(ERROR) << "Failed to get the current kernel boot disk name";
+    return vector<string>();
+  }
+
+  vector<string> devices;
+  for (int partition_num : {2, 4}) {  // for now, only #2, #4 for slot A & B
+    string device = utils::MakePartitionName(disk_name, partition_num);
+    if (!device.empty()) {
+      devices.push_back(std::move(device));
+    } else {
+      LOG(ERROR) << "Cannot make a partition name for disk: "
+                 << disk_name << ", partition: " << partition_num;
+    }
+  }
+
+  return devices;
+}
+
+
+bool Hardware::MarkKernelUnbootable(const string& kernel_device) {
+  LOG(INFO) << "MarkPartitionUnbootable: " << kernel_device;
+
+  if (kernel_device == BootKernelDevice()) {
+    LOG(ERROR) << "Refusing to mark current kernel as unbootable.";
+    return false;
+  }
+
+  string disk_name;
+  int partition_num = 0;
+
+  if (!utils::SplitPartitionName(kernel_device, &disk_name, &partition_num))
+    return false;
+
+  CgptAddParams params;
+  memset(&params, 0, sizeof(params));
+
+  params.drive_name = const_cast<char *>(disk_name.c_str());
+  params.partition = partition_num;
+
+  params.successful = false;
+  params.set_successful = true;
+
+  params.tries = 0;
+  params.set_tries = true;
+
+  int retval = CgptSetAttributes(&params);
+  if (retval != CGPT_OK) {
+    LOG(ERROR) << "Marking kernel unbootable failed.";
+    return false;
+  }
+
+  return true;
+}
+
+bool Hardware::IsOfficialBuild() const {
+  return VbGetSystemPropertyInt("debug_build") == 0;
+}
+
+bool Hardware::IsNormalBootMode() const {
+  bool dev_mode = VbGetSystemPropertyInt("devsw_boot") != 0;
+  LOG_IF(INFO, dev_mode) << "Booted in dev mode.";
+  return !dev_mode;
+}
+
+bool Hardware::IsOOBEComplete(base::Time* out_time_of_oobe) const {
+  struct stat statbuf;
+  if (stat(kOOBECompletedMarker, &statbuf) != 0) {
+    if (errno != ENOENT) {
+      PLOG(ERROR) << "Error getting information about "
+                  << kOOBECompletedMarker;
+    }
+    return false;
+  }
+
+  if (out_time_of_oobe != nullptr)
+    *out_time_of_oobe = base::Time::FromTimeT(statbuf.st_mtime);
+  return true;
+}
+
+static string ReadValueFromCrosSystem(const string& key) {
+  char value_buffer[VB_MAX_STRING_PROPERTY];
+
+  const char *rv = VbGetSystemPropertyString(key.c_str(), value_buffer,
+                                             sizeof(value_buffer));
+  if (rv != nullptr) {
+    string return_value(value_buffer);
+    base::TrimWhitespaceASCII(return_value, base::TRIM_ALL, &return_value);
+    return return_value;
+  }
+
+  LOG(ERROR) << "Unable to read crossystem key " << key;
+  return "";
+}
+
+string Hardware::GetHardwareClass() const {
+  if (USE_HWID_OVERRIDE) {
+    return HwidOverride::Read(base::FilePath("/"));
+  }
+  return ReadValueFromCrosSystem("hwid");
+}
+
+string Hardware::GetFirmwareVersion() const {
+  return ReadValueFromCrosSystem("fwid");
+}
+
+string Hardware::GetECVersion() const {
+  string input_line;
+  int exit_code = 0;
+  vector<string> cmd = {"/usr/sbin/mosys", "-k", "ec", "info"};
+
+  bool success = Subprocess::SynchronousExec(cmd, &exit_code, &input_line);
+  if (!success || exit_code) {
+    LOG(ERROR) << "Unable to read ec info from mosys (" << exit_code << ")";
+    return "";
+  }
+
+  return utils::ParseECVersion(input_line);
+}
+
+int Hardware::GetPowerwashCount() const {
+  int powerwash_count;
+  string contents;
+  if (!utils::ReadFile(kPowerwashCountMarker, &contents))
+    return -1;
+  base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents);
+  if (!base::StringToInt(contents, &powerwash_count))
+    return -1;
+  return powerwash_count;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/hardware.h b/hardware.h
new file mode 100644
index 0000000..c573117
--- /dev/null
+++ b/hardware.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2013 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_HARDWARE_H_
+#define UPDATE_ENGINE_HARDWARE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements the real interface with the hardware.
+class Hardware : public HardwareInterface {
+ public:
+  Hardware();
+  ~Hardware() override;
+
+  // HardwareInterface methods.
+  std::string BootKernelDevice() const override;
+  std::string BootDevice() const override;
+  bool IsBootDeviceRemovable() const override;
+  std::vector<std::string> GetKernelDevices() const override;
+  bool IsKernelBootable(const std::string& kernel_device,
+                        bool* bootable) const override;
+  bool MarkKernelUnbootable(const std::string& kernel_device) override;
+  bool IsOfficialBuild() const override;
+  bool IsNormalBootMode() const override;
+  bool IsOOBEComplete(base::Time* out_time_of_oobe) const override;
+  std::string GetHardwareClass() const override;
+  std::string GetFirmwareVersion() const override;
+  std::string GetECVersion() const override;
+  int GetPowerwashCount() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Hardware);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_HARDWARE_H_
diff --git a/hardware_interface.h b/hardware_interface.h
new file mode 100644
index 0000000..d5a1f1f
--- /dev/null
+++ b/hardware_interface.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 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_HARDWARE_INTERFACE_H_
+#define UPDATE_ENGINE_HARDWARE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The hardware interface allows access to the following parts of the system,
+// closely related to the hardware:
+//  * crossystem exposed properties: firmware, hwid, etc.
+//  * Physical disk: partition booted from and partition name conversions.
+// These stateless functions are tied together in this interface to facilitate
+// unit testing.
+class HardwareInterface {
+ public:
+  virtual ~HardwareInterface() {}
+
+  // Returns the currently booted kernel partition. "/dev/sda2", for example.
+  virtual std::string BootKernelDevice() const = 0;
+
+  // Returns the currently booted rootfs partition. "/dev/sda3", for example.
+  virtual std::string BootDevice() const = 0;
+
+  // Return whether the BootDevice() is a removable device.
+  virtual bool IsBootDeviceRemovable() const = 0;
+
+  // Returns a list of all kernel partitions available (whether bootable or not)
+  virtual std::vector<std::string> GetKernelDevices() const = 0;
+
+  // Is the specified kernel partition currently bootable, based on GPT flags?
+  // Returns success.
+  virtual bool IsKernelBootable(const std::string& kernel_device,
+                                bool* bootable) const = 0;
+
+  // Mark the specified kernel partition unbootable in GPT flags. We mark
+  // the other kernel as bootable inside postinst, not inside the UE.
+  // Returns success.
+  virtual bool MarkKernelUnbootable(const std::string& kernel_device) = 0;
+
+  // Returns true if this is an official Chrome OS build, false otherwise.
+  virtual bool IsOfficialBuild() const = 0;
+
+  // Returns true if the boot mode is normal or if it's unable to
+  // determine the boot mode. Returns false if the boot mode is
+  // developer.
+  virtual bool IsNormalBootMode() const = 0;
+
+  // Returns true if the OOBE process has been completed and EULA accepted,
+  // False otherwise. If True is returned, and |out_time_of_oobe| isn't null,
+  // the time-stamp of when OOBE happened is stored at |out_time_of_oobe|.
+  virtual bool IsOOBEComplete(base::Time* out_time_of_oobe) const = 0;
+
+  // Returns the HWID or an empty string on error.
+  virtual std::string GetHardwareClass() const = 0;
+
+  // Returns the firmware version or an empty string if the system is
+  // not running chrome os firmware.
+  virtual std::string GetFirmwareVersion() const = 0;
+
+  // Returns the ec version or an empty string if the system is not
+  // running a custom chrome os ec.
+  virtual std::string GetECVersion() const = 0;
+
+  // Returns the powerwash_count from the stateful. If the file is not found
+  // or is invalid, returns -1. Brand new machines out of the factory or after
+  // recovery don't have this value set.
+  virtual int GetPowerwashCount() const = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_HARDWARE_INTERFACE_H_
diff --git a/http_common.cc b/http_common.cc
new file mode 100644
index 0000000..7737f4c
--- /dev/null
+++ b/http_common.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2009 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.
+
+// Implementation of common HTTP related functions.
+
+#include "update_engine/http_common.h"
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+const char *GetHttpResponseDescription(HttpResponseCode code) {
+  static const struct {
+    HttpResponseCode code;
+    const char* description;
+  } http_response_table[] = {
+    { kHttpResponseOk,                  "OK" },
+    { kHttpResponseCreated,             "Created" },
+    { kHttpResponseAccepted,            "Accepted" },
+    { kHttpResponseNonAuthInfo,         "Non-Authoritative Information" },
+    { kHttpResponseNoContent,           "No Content" },
+    { kHttpResponseResetContent,        "Reset Content" },
+    { kHttpResponsePartialContent,      "Partial Content" },
+    { kHttpResponseMultipleChoices,     "Multiple Choices" },
+    { kHttpResponseMovedPermanently,    "Moved Permanently" },
+    { kHttpResponseFound,               "Found" },
+    { kHttpResponseSeeOther,            "See Other" },
+    { kHttpResponseNotModified,         "Not Modified" },
+    { kHttpResponseUseProxy,            "Use Proxy" },
+    { kHttpResponseTempRedirect,        "Temporary Redirect" },
+    { kHttpResponseBadRequest,          "Bad Request" },
+    { kHttpResponseUnauth,              "Unauthorized" },
+    { kHttpResponseForbidden,           "Forbidden" },
+    { kHttpResponseNotFound,            "Not Found" },
+    { kHttpResponseRequestTimeout,      "Request Timeout" },
+    { kHttpResponseInternalServerError, "Internal Server Error" },
+    { kHttpResponseNotImplemented,      "Not Implemented" },
+    { kHttpResponseServiceUnavailable,  "Service Unavailable" },
+    { kHttpResponseVersionNotSupported, "HTTP Version Not Supported" },
+  };
+
+  bool is_found = false;
+  size_t i;
+  for (i = 0; i < arraysize(http_response_table); i++)
+    if ((is_found = (http_response_table[i].code == code)))
+      break;
+
+  return (is_found ? http_response_table[i].description : "(unsupported)");
+}
+
+HttpResponseCode StringToHttpResponseCode(const char *s) {
+  return static_cast<HttpResponseCode>(strtoul(s, nullptr, 10));
+}
+
+
+const char *GetHttpContentTypeString(HttpContentType type) {
+  static const struct {
+    HttpContentType type;
+    const char* str;
+  } http_content_type_table[] = {
+    { kHttpContentTypeTextXml, "text/xml" },
+  };
+
+  bool is_found = false;
+  size_t i;
+  for (i = 0; i < arraysize(http_content_type_table); i++)
+    if ((is_found = (http_content_type_table[i].type == type)))
+      break;
+
+  return (is_found ? http_content_type_table[i].str : nullptr);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/http_common.h b/http_common.h
new file mode 100644
index 0000000..a4f7bdc
--- /dev/null
+++ b/http_common.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2009 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.
+
+// This file contains general definitions used in implementing, testing and
+// emulating communication over HTTP.
+
+#ifndef UPDATE_ENGINE_HTTP_COMMON_H_
+#define UPDATE_ENGINE_HTTP_COMMON_H_
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+// Enumeration type for HTTP response codes.
+enum HttpResponseCode {
+  kHttpResponseUndefined           = 0,
+  kHttpResponseOk                  = 200,
+  kHttpResponseCreated             = 201,
+  kHttpResponseAccepted            = 202,
+  kHttpResponseNonAuthInfo         = 203,
+  kHttpResponseNoContent           = 204,
+  kHttpResponseResetContent        = 205,
+  kHttpResponsePartialContent      = 206,
+  kHttpResponseMultipleChoices     = 300,
+  kHttpResponseMovedPermanently    = 301,
+  kHttpResponseFound               = 302,
+  kHttpResponseSeeOther            = 303,
+  kHttpResponseNotModified         = 304,
+  kHttpResponseUseProxy            = 305,
+  kHttpResponseTempRedirect        = 307,
+  kHttpResponseBadRequest          = 400,
+  kHttpResponseUnauth              = 401,
+  kHttpResponseForbidden           = 403,
+  kHttpResponseNotFound            = 404,
+  kHttpResponseRequestTimeout      = 408,
+  kHttpResponseReqRangeNotSat      = 416,
+  kHttpResponseInternalServerError = 500,
+  kHttpResponseNotImplemented      = 501,
+  kHttpResponseServiceUnavailable  = 503,
+  kHttpResponseVersionNotSupported = 505,
+};
+
+// Returns a standard HTTP status line string for a given response code.
+const char *GetHttpResponseDescription(HttpResponseCode code);
+
+// Converts a string beginning with an HTTP error code into numerical value.
+HttpResponseCode StringToHttpResponseCode(const char *s);
+
+
+// Enumeration type for HTTP Content-Type.
+enum HttpContentType {
+  kHttpContentTypeUnspecified = 0,
+  kHttpContentTypeTextXml,
+};
+
+// Returns a standard HTTP Content-Type string.
+const char *GetHttpContentTypeString(HttpContentType type);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_HTTP_COMMON_H_
diff --git a/http_fetcher.cc b/http_fetcher.cc
new file mode 100644
index 0000000..b355fdb
--- /dev/null
+++ b/http_fetcher.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2009 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/http_fetcher.h"
+
+#include <base/bind.h>
+
+using base::Closure;
+using chromeos::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+HttpFetcher::~HttpFetcher() {
+  if (no_resolver_idle_id_ != MessageLoop::kTaskIdNull) {
+    MessageLoop::current()->CancelTask(no_resolver_idle_id_);
+    no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+  }
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size,
+                              HttpContentType type) {
+  post_data_set_ = true;
+  post_data_.clear();
+  const char* char_data = reinterpret_cast<const char*>(data);
+  post_data_.insert(post_data_.end(), char_data, char_data + size);
+  post_content_type_ = type;
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size) {
+  SetPostData(data, size, kHttpContentTypeUnspecified);
+}
+
+// Proxy methods to set the proxies, then to pop them off.
+bool HttpFetcher::ResolveProxiesForUrl(const string& url,
+                                       const Closure& callback) {
+  CHECK_EQ(static_cast<Closure*>(nullptr), callback_.get());
+  callback_.reset(new Closure(callback));
+
+  if (!proxy_resolver_) {
+    LOG(INFO) << "Not resolving proxies (no proxy resolver).";
+    no_resolver_idle_id_ = MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(&HttpFetcher::NoProxyResolverCallback,
+                   base::Unretained(this)));
+    return true;
+  }
+  return proxy_resolver_->GetProxiesForUrl(url,
+                                           &HttpFetcher::StaticProxiesResolved,
+                                           this);
+}
+
+void HttpFetcher::NoProxyResolverCallback() {
+  ProxiesResolved(deque<string>());
+}
+
+void HttpFetcher::ProxiesResolved(const deque<string>& proxies) {
+  no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+  if (!proxies.empty())
+    SetProxies(proxies);
+  CHECK_NE(static_cast<Closure*>(nullptr), callback_.get());
+  Closure* callback = callback_.release();
+  // This may indirectly call back into ResolveProxiesForUrl():
+  callback->Run();
+  delete callback;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/http_fetcher.h b/http_fetcher.h
new file mode 100644
index 0000000..0d19a9a
--- /dev/null
+++ b/http_fetcher.h
@@ -0,0 +1,195 @@
+// Copyright (c) 2009 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_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_HTTP_FETCHER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/http_common.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/system_state.h"
+
+// This class is a simple wrapper around an HTTP library (libcurl). We can
+// easily mock out this interface for testing.
+
+// Implementations of this class should use asynchronous i/o. They can access
+// the MessageLoop to request callbacks when timers or file descriptors change.
+
+namespace chromeos_update_engine {
+
+class HttpFetcherDelegate;
+
+class HttpFetcher {
+ public:
+  // |proxy_resolver| is the resolver that will be consulted for proxy
+  // settings. It may be null, in which case direct connections will
+  // be used. Does not take ownership of the resolver.
+  HttpFetcher(ProxyResolver* proxy_resolver, SystemState* system_state)
+      : post_data_set_(false),
+        http_response_code_(0),
+        delegate_(nullptr),
+        proxies_(1, kNoProxy),
+        proxy_resolver_(proxy_resolver),
+        callback_(nullptr),
+        system_state_(system_state) {}
+  virtual ~HttpFetcher();
+
+  void set_delegate(HttpFetcherDelegate* delegate) { delegate_ = delegate; }
+  HttpFetcherDelegate* delegate() const { return delegate_; }
+  int http_response_code() const { return http_response_code_; }
+
+  // Optional: Post data to the server. The HttpFetcher should make a copy
+  // of this data and upload it via HTTP POST during the transfer. The type of
+  // the data is necessary for properly setting the Content-Type HTTP header.
+  void SetPostData(const void* data, size_t size, HttpContentType type);
+
+  // Same without a specified Content-Type.
+  void SetPostData(const void* data, size_t size);
+
+  // Proxy methods to set the proxies, then to pop them off.
+  // Returns true on success.
+  bool ResolveProxiesForUrl(const std::string& url,
+                            const base::Closure& callback);
+
+  void SetProxies(const std::deque<std::string>& proxies) {
+    proxies_ = proxies;
+  }
+  const std::string& GetCurrentProxy() const {
+    return proxies_.front();
+  }
+  bool HasProxy() const { return !proxies_.empty(); }
+  void PopProxy() { proxies_.pop_front(); }
+
+  // Downloading should resume from this offset
+  virtual void SetOffset(off_t offset) = 0;
+
+  // Set/unset the length of the range to be downloaded.
+  virtual void SetLength(size_t length) = 0;
+  virtual void UnsetLength() = 0;
+
+  // Begins the transfer to the specified URL. This fetcher instance should not
+  // be destroyed until either TransferComplete, or TransferTerminated is
+  // called.
+  virtual void BeginTransfer(const std::string& url) = 0;
+
+  // Aborts the transfer. The transfer may not abort right away -- delegate's
+  // TransferTerminated() will be called when the transfer is actually done.
+  virtual void TerminateTransfer() = 0;
+
+  // If data is coming in too quickly, you can call Pause() to pause the
+  // transfer. The delegate will not have ReceivedBytes() called while
+  // an HttpFetcher is paused.
+  virtual void Pause() = 0;
+
+  // Used to unpause an HttpFetcher and let the bytes stream in again.
+  // If a delegate is set, ReceivedBytes() may be called on it before
+  // Unpause() returns
+  virtual void Unpause() = 0;
+
+  // These two function are overloaded in LibcurlHttp fetcher to speed
+  // testing.
+  virtual void set_idle_seconds(int seconds) {}
+  virtual void set_retry_seconds(int seconds) {}
+
+  // Sets the values used to time out the connection if the transfer
+  // rate is less than |low_speed_bps| bytes/sec for more than
+  // |low_speed_sec| seconds.
+  virtual void set_low_speed_limit(int low_speed_bps, int low_speed_sec) = 0;
+
+  // Sets the connect timeout, e.g. the maximum amount of time willing
+  // to wait for establishing a connection to the server.
+  virtual void set_connect_timeout(int connect_timeout_seconds) = 0;
+
+  // Sets the number of allowed retries.
+  virtual void set_max_retry_count(int max_retry_count) = 0;
+
+  // Get the total number of bytes downloaded by fetcher.
+  virtual size_t GetBytesDownloaded() = 0;
+
+  ProxyResolver* proxy_resolver() const { return proxy_resolver_; }
+
+  // Returns the global SystemState.
+  SystemState* GetSystemState() {
+    return system_state_;
+  }
+
+ protected:
+  // The URL we're actively fetching from
+  std::string url_;
+
+  // POST data for the transfer, and whether or not it was ever set
+  bool post_data_set_;
+  chromeos::Blob post_data_;
+  HttpContentType post_content_type_;
+
+  // The server's HTTP response code from the last transfer. This
+  // field should be set to 0 when a new transfer is initiated, and
+  // set to the response code when the transfer is complete.
+  int http_response_code_;
+
+  // The delegate; may be null.
+  HttpFetcherDelegate* delegate_;
+
+  // Proxy servers
+  std::deque<std::string> proxies_;
+
+  ProxyResolver* const proxy_resolver_;
+
+  // The ID of the idle callback, used when we have no proxy resolver.
+  chromeos::MessageLoop::TaskId no_resolver_idle_id_{
+      chromeos::MessageLoop::kTaskIdNull};
+
+  // Callback for when we are resolving proxies
+  std::unique_ptr<base::Closure> callback_;
+
+  // Global system context.
+  SystemState* system_state_;
+
+ private:
+  // Callback from the proxy resolver
+  void ProxiesResolved(const std::deque<std::string>& proxies);
+  static void StaticProxiesResolved(const std::deque<std::string>& proxies,
+                                    void* data) {
+    reinterpret_cast<HttpFetcher*>(data)->ProxiesResolved(proxies);
+  }
+
+  // Callback used to run the proxy resolver callback when there is no
+  // |proxy_resolver_|.
+  void NoProxyResolverCallback();
+
+  DISALLOW_COPY_AND_ASSIGN(HttpFetcher);
+};
+
+// Interface for delegates
+class HttpFetcherDelegate {
+ public:
+  virtual ~HttpFetcherDelegate() = default;
+
+  // Called every time bytes are received.
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const void* bytes,
+                             size_t length) = 0;
+
+  // Called if the fetcher seeks to a particular offset.
+  virtual void SeekToOffset(off_t offset) {}
+
+  // When a transfer has completed, exactly one of these two methods will be
+  // called. TransferTerminated is called when the transfer has been aborted
+  // through TerminateTransfer. TransferComplete is called in all other
+  // situations. It's OK to destroy the |fetcher| object in this callback.
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
+  virtual void TransferTerminated(HttpFetcher* fetcher) {}
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_HTTP_FETCHER_H_
diff --git a/http_fetcher_unittest.cc b/http_fetcher_unittest.cc
new file mode 100644
index 0000000..fd520e9
--- /dev/null
+++ b/http_fetcher_unittest.cc
@@ -0,0 +1,1099 @@
+// Copyright (c) 2012 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 <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/http_common.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/multi_range_http_fetcher.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+const int kBigLength           = 100000;
+const int kMediumLength        = 1000;
+const int kFlakyTruncateLength = 29000;
+const int kFlakySleepEvery     = 3;
+const int kFlakySleepSecs      = 10;
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+static const char *kUnusedUrl = "unused://unused";
+
+static inline string LocalServerUrlForPath(in_port_t port,
+                                           const string& path) {
+  string port_str = (port ? base::StringPrintf(":%hu", port) : "");
+  return base::StringPrintf("http://127.0.0.1%s%s", port_str.c_str(),
+                            path.c_str());
+}
+
+//
+// Class hierarchy for HTTP server implementations.
+//
+
+class HttpServer {
+ public:
+  // This makes it an abstract class (dirty but works).
+  virtual ~HttpServer() = 0;
+
+  virtual in_port_t GetPort() const {
+    return 0;
+  }
+
+  bool started_;
+};
+
+HttpServer::~HttpServer() {}
+
+
+class NullHttpServer : public HttpServer {
+ public:
+  NullHttpServer() {
+    started_ = true;
+  }
+};
+
+
+class PythonHttpServer : public HttpServer {
+ public:
+  PythonHttpServer() : pid_(-1), port_(0) {
+    started_ = false;
+
+    // Spawn the server process.
+    gchar *argv[] = {
+      const_cast<gchar*>("./test_http_server"),
+      nullptr
+    };
+    GError *err;
+    gint server_stdout = -1;
+    if (!g_spawn_async_with_pipes(nullptr, argv, nullptr,
+                                  G_SPAWN_DO_NOT_REAP_CHILD, nullptr, nullptr,
+                                  &pid_, nullptr, &server_stdout, nullptr,
+                                  &err)) {
+      LOG(ERROR) << "failed to spawn http server process";
+      return;
+    }
+    CHECK_GT(pid_, 0);
+    CHECK_GE(server_stdout, 0);
+    LOG(INFO) << "started http server with pid " << pid_;
+
+    // Wait for server to begin accepting connections, obtain its port.
+    char line[80];
+    const size_t listening_msg_prefix_len = strlen(kServerListeningMsgPrefix);
+    CHECK_GT(sizeof(line), listening_msg_prefix_len);
+    int line_len = read(server_stdout, line, sizeof(line) - 1);
+    if (line_len <= static_cast<int>(listening_msg_prefix_len)) {
+      if (line_len < 0) {
+        PLOG(ERROR) << "error reading http server stdout";
+      } else {
+        LOG(ERROR) << "server output too short";
+      }
+      Terminate(true);
+      return;
+    }
+
+    line[line_len] = '\0';
+    CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+    const char* listening_port_str = line + listening_msg_prefix_len;
+    char* end_ptr;
+    long raw_port = strtol(listening_port_str,  // NOLINT(runtime/int)
+                           &end_ptr, 10);
+    CHECK(!*end_ptr || *end_ptr == '\n');
+    port_ = static_cast<in_port_t>(raw_port);
+    CHECK_GT(port_, 0);
+    started_ = true;
+    LOG(INFO) << "server running, listening on port " << port_;
+    LOG(INFO) << "gdb attach now!";
+  }
+
+  ~PythonHttpServer() {
+    // If there's no process, do nothing.
+    if (pid_ == -1)
+      return;
+
+    // If server is responsive, request that it gracefully terminate.
+    bool do_kill = false;
+    if (started_) {
+      LOG(INFO) << "running wget to exit";
+      if (system((string("wget -t 1 --output-document=/dev/null ") +
+                  LocalServerUrlForPath(port_, "/quitquitquit")).c_str())) {
+        LOG(WARNING) << "wget failed, resorting to brute force";
+        do_kill = true;
+      }
+    }
+
+    // Server not responding or wget failed, kill the process.
+    Terminate(do_kill);
+  }
+
+  in_port_t GetPort() const override {
+    return port_;
+  }
+
+ private:
+  void Terminate(bool do_kill) {
+    ASSERT_GT(pid_, 0);
+
+    if (do_kill) {
+      LOG(INFO) << "terminating (SIGKILL) server process with pid " << pid_;
+      kill(pid_, SIGKILL);
+    }
+
+    LOG(INFO) << "waiting for http server with pid " << pid_ << " to terminate";
+    int status;
+    pid_t killed_pid = waitpid(pid_, &status, 0);
+    ASSERT_EQ(killed_pid, pid_);
+    LOG(INFO) << "http server with pid " << pid_
+              << " terminated with status " << status;
+    pid_ = -1;
+  }
+
+  static const char* kServerListeningMsgPrefix;
+
+  GPid pid_;
+  in_port_t port_;
+};
+
+const char* PythonHttpServer::kServerListeningMsgPrefix = "listening on port ";
+
+//
+// Class hierarchy for HTTP fetcher test wrappers.
+//
+
+class AnyHttpFetcherTest {
+ public:
+  AnyHttpFetcherTest() {}
+  virtual ~AnyHttpFetcherTest() {}
+
+  virtual HttpFetcher* NewLargeFetcher(size_t num_proxies) = 0;
+  HttpFetcher* NewLargeFetcher() {
+    return NewLargeFetcher(1);
+  }
+
+  virtual HttpFetcher* NewSmallFetcher(size_t num_proxies) = 0;
+  HttpFetcher* NewSmallFetcher() {
+    return NewSmallFetcher(1);
+  }
+
+  virtual string BigUrl(in_port_t port) const { return kUnusedUrl; }
+  virtual string SmallUrl(in_port_t port) const { return kUnusedUrl; }
+  virtual string ErrorUrl(in_port_t port) const { return kUnusedUrl; }
+
+  virtual bool IsMock() const = 0;
+  virtual bool IsMulti() const = 0;
+
+  virtual void IgnoreServerAborting(HttpServer* server) const {}
+
+  virtual HttpServer* CreateServer() = 0;
+
+ protected:
+  DirectProxyResolver proxy_resolver_;
+  FakeSystemState fake_system_state_;
+};
+
+class MockHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewLargeFetcher;
+  HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+    chromeos::Blob big_data(1000000);
+    CHECK_GT(num_proxies, 0u);
+    proxy_resolver_.set_num_proxies(num_proxies);
+    return new MockHttpFetcher(
+        big_data.data(),
+        big_data.size(),
+        reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+  }
+
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewSmallFetcher;
+  HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+    CHECK_GT(num_proxies, 0u);
+    proxy_resolver_.set_num_proxies(num_proxies);
+    return new MockHttpFetcher(
+        "x",
+        1,
+        reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+  }
+
+  bool IsMock() const override { return true; }
+  bool IsMulti() const override { return false; }
+
+  HttpServer* CreateServer() override {
+    return new NullHttpServer;
+  }
+};
+
+class LibcurlHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewLargeFetcher;
+  HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+    CHECK_GT(num_proxies, 0u);
+    proxy_resolver_.set_num_proxies(num_proxies);
+    LibcurlHttpFetcher *ret = new
+        LibcurlHttpFetcher(reinterpret_cast<ProxyResolver*>(&proxy_resolver_),
+                           &fake_system_state_);
+    // Speed up test execution.
+    ret->set_idle_seconds(1);
+    ret->set_retry_seconds(1);
+    fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+    return ret;
+  }
+
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewSmallFetcher;
+  HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+    return NewLargeFetcher(num_proxies);
+  }
+
+  string BigUrl(in_port_t port) const override {
+    return LocalServerUrlForPath(port,
+                                 base::StringPrintf("/download/%d",
+                                                    kBigLength));
+  }
+  string SmallUrl(in_port_t port) const override {
+    return LocalServerUrlForPath(port, "/foo");
+  }
+  string ErrorUrl(in_port_t port) const override {
+    return LocalServerUrlForPath(port, "/error");
+  }
+
+  bool IsMock() const override { return false; }
+  bool IsMulti() const override { return false; }
+
+  void IgnoreServerAborting(HttpServer* server) const override {
+    // Nothing to do.
+  }
+
+  HttpServer* CreateServer() override {
+    return new PythonHttpServer;
+  }
+};
+
+class MultiRangeHttpFetcherTest : public LibcurlHttpFetcherTest {
+ public:
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewLargeFetcher;
+  HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+    CHECK_GT(num_proxies, 0u);
+    proxy_resolver_.set_num_proxies(num_proxies);
+    ProxyResolver* resolver =
+        reinterpret_cast<ProxyResolver*>(&proxy_resolver_);
+    MultiRangeHttpFetcher *ret =
+        new MultiRangeHttpFetcher(
+            new LibcurlHttpFetcher(resolver, &fake_system_state_));
+    ret->ClearRanges();
+    ret->AddRange(0);
+    // Speed up test execution.
+    ret->set_idle_seconds(1);
+    ret->set_retry_seconds(1);
+    fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+    return ret;
+  }
+
+  // Necessary to unhide the definition in the base class.
+  using AnyHttpFetcherTest::NewSmallFetcher;
+  HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+    return NewLargeFetcher(num_proxies);
+  }
+
+  bool IsMulti() const override { return true; }
+};
+
+
+//
+// Infrastructure for type tests of HTTP fetcher.
+// See: http://code.google.com/p/googletest/wiki/AdvancedGuide#Typed_Tests
+//
+
+// Fixture class template. We use an explicit constraint to guarantee that it
+// can only be instantiated with an AnyHttpFetcherTest type, see:
+// http://www2.research.att.com/~bs/bs_faq2.html#constraints
+template <typename T>
+class HttpFetcherTest : public ::testing::Test {
+ public:
+  // TODO(deymo): Replace this with a FakeMessageLoop. We can't do that yet
+  // because these tests use g_spawn_async_with_pipes() to launch the
+  // http_test_server.
+  chromeos::GlibMessageLoop loop_;
+
+  T test_;
+
+ protected:
+  HttpFetcherTest() {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+ private:
+  static void TypeConstraint(T* a) {
+    AnyHttpFetcherTest *b = a;
+    if (b == 0)  // Silence compiler warning of unused variable.
+      *b = a;
+  }
+};
+
+// Test case types list.
+typedef ::testing::Types<LibcurlHttpFetcherTest,
+                         MockHttpFetcherTest,
+                         MultiRangeHttpFetcherTest> HttpFetcherTestTypes;
+TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
+
+
+namespace {
+class HttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  HttpFetcherTestDelegate() :
+      is_expect_error_(false), times_transfer_complete_called_(0),
+      times_transfer_terminated_called_(0), times_received_bytes_called_(0) {}
+
+  void ReceivedBytes(HttpFetcher* /* fetcher */,
+                     const void* /* bytes */, size_t /* length */) override {
+    // Update counters
+    times_received_bytes_called_++;
+  }
+
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    if (is_expect_error_)
+      EXPECT_EQ(kHttpResponseNotFound, fetcher->http_response_code());
+    else
+      EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+    MessageLoop::current()->BreakLoop();
+
+    // Update counter
+    times_transfer_complete_called_++;
+  }
+
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+    times_transfer_terminated_called_++;
+  }
+
+  // Are we expecting an error response? (default: no)
+  bool is_expect_error_;
+
+  // Counters for callback invocations.
+  int times_transfer_complete_called_;
+  int times_transfer_terminated_called_;
+  int times_received_bytes_called_;
+};
+
+
+void StartTransfer(HttpFetcher* http_fetcher, const string& url) {
+  http_fetcher->BeginTransfer(url);
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleTest) {
+  HttpFetcherTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+  fetcher->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  this->loop_.PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      this->test_.SmallUrl(server->GetPort())));
+  this->loop_.Run();
+}
+
+TYPED_TEST(HttpFetcherTest, SimpleBigTest) {
+  HttpFetcherTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+  fetcher->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  this->loop_.PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      this->test_.BigUrl(server->GetPort())));
+  this->loop_.Run();
+}
+
+// Issue #9648: when server returns an error HTTP response, the fetcher needs to
+// terminate transfer prematurely, rather than try to process the error payload.
+TYPED_TEST(HttpFetcherTest, ErrorTest) {
+  if (this->test_.IsMock() || this->test_.IsMulti())
+    return;
+  HttpFetcherTestDelegate delegate;
+
+  // Delegate should expect an error response.
+  delegate.is_expect_error_ = true;
+
+  unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+  fetcher->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  this->loop_.PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      this->test_.ErrorUrl(server->GetPort())));
+  this->loop_.Run();
+
+  // Make sure that no bytes were received.
+  CHECK_EQ(delegate.times_received_bytes_called_, 0);
+  CHECK_EQ(fetcher->GetBytesDownloaded(), static_cast<size_t>(0));
+
+  // Make sure that transfer completion was signaled once, and no termination
+  // was signaled.
+  CHECK_EQ(delegate.times_transfer_complete_called_, 1);
+  CHECK_EQ(delegate.times_transfer_terminated_called_, 0);
+}
+
+namespace {
+class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* /* bytes */, size_t /* length */) override {
+    CHECK(!paused_);
+    paused_ = true;
+    fetcher->Pause();
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  void Unpause() {
+    CHECK(paused_);
+    paused_ = false;
+    fetcher_->Unpause();
+  }
+  bool paused_;
+  HttpFetcher* fetcher_;
+};
+
+void UnpausingTimeoutCallback(PausingHttpFetcherTestDelegate* delegate,
+                              MessageLoop::TaskId* my_id) {
+  if (delegate->paused_)
+    delegate->Unpause();
+  // Update the task id with the new scheduled callback.
+  *my_id = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UnpausingTimeoutCallback, delegate, my_id),
+      base::TimeDelta::FromMilliseconds(200));
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, PauseTest) {
+  {
+    PausingHttpFetcherTestDelegate delegate;
+    unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+    delegate.paused_ = false;
+    delegate.fetcher_ = fetcher.get();
+    fetcher->set_delegate(&delegate);
+
+    unique_ptr<HttpServer> server(this->test_.CreateServer());
+    ASSERT_TRUE(server->started_);
+
+    MessageLoop::TaskId callback_id;
+    callback_id = this->loop_.PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&UnpausingTimeoutCallback, &delegate, &callback_id),
+        base::TimeDelta::FromMilliseconds(200));
+    fetcher->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+    this->loop_.Run();
+    EXPECT_TRUE(this->loop_.CancelTask(callback_id));
+  }
+}
+
+namespace {
+class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {}
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    ADD_FAILURE();  // We should never get here
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    EXPECT_EQ(fetcher, fetcher_.get());
+    EXPECT_FALSE(once_);
+    EXPECT_TRUE(callback_once_);
+    callback_once_ = false;
+    // The fetcher could have a callback scheduled on the ProxyResolver that
+    // can fire after this callback. We wait until the end of the test to
+    // delete the fetcher.
+  }
+  void TerminateTransfer() {
+    CHECK(once_);
+    once_ = false;
+    fetcher_->TerminateTransfer();
+  }
+  void EndLoop() {
+    MessageLoop::current()->BreakLoop();
+  }
+  bool once_;
+  bool callback_once_;
+  unique_ptr<HttpFetcher> fetcher_;
+};
+
+void AbortingTimeoutCallback(AbortingHttpFetcherTestDelegate* delegate,
+                             MessageLoop::TaskId* my_id) {
+  if (delegate->once_) {
+    delegate->TerminateTransfer();
+    *my_id = MessageLoop::current()->PostTask(
+        FROM_HERE,
+        base::Bind(AbortingTimeoutCallback, delegate, my_id));
+  } else {
+    delegate->EndLoop();
+    *my_id = MessageLoop::kTaskIdNull;
+  }
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, AbortTest) {
+  AbortingHttpFetcherTestDelegate delegate;
+  delegate.fetcher_.reset(this->test_.NewLargeFetcher());
+  delegate.once_ = true;
+  delegate.callback_once_ = true;
+  delegate.fetcher_->set_delegate(&delegate);
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  this->test_.IgnoreServerAborting(server.get());
+  ASSERT_TRUE(server->started_);
+
+  MessageLoop::TaskId task_id = MessageLoop::kTaskIdNull;
+
+  task_id = this->loop_.PostTask(
+      FROM_HERE,
+      base::Bind(AbortingTimeoutCallback, &delegate, &task_id));
+  delegate.fetcher_->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+  this->loop_.Run();
+  CHECK(!delegate.once_);
+  CHECK(!delegate.callback_once_);
+  this->loop_.CancelTask(task_id);
+}
+
+namespace {
+class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    data.append(reinterpret_cast<const char*>(bytes), length);
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_TRUE(successful);
+    EXPECT_EQ(kHttpResponsePartialContent, fetcher->http_response_code());
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  string data;
+};
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, FlakyTest) {
+  if (this->test_.IsMock())
+    return;
+  {
+    FlakyHttpFetcherTestDelegate delegate;
+    unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    unique_ptr<HttpServer> server(this->test_.CreateServer());
+    ASSERT_TRUE(server->started_);
+
+    this->loop_.PostTask(FROM_HERE, base::Bind(
+        &StartTransfer,
+        fetcher.get(),
+        LocalServerUrlForPath(server->GetPort(),
+                              base::StringPrintf("/flaky/%d/%d/%d/%d",
+                                                 kBigLength,
+                                                 kFlakyTruncateLength,
+                                                 kFlakySleepEvery,
+                                                 kFlakySleepSecs))));
+    this->loop_.Run();
+
+    // verify the data we get back
+    ASSERT_EQ(kBigLength, delegate.data.size());
+    for (int i = 0; i < kBigLength; i += 10) {
+      // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+      ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+    }
+  }
+}
+
+namespace {
+// This delegate kills the server attached to it after receiving any bytes.
+// This can be used for testing what happens when you try to fetch data and
+// the server dies.
+class FailureHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  explicit FailureHttpFetcherTestDelegate(PythonHttpServer* server)
+      : server_(server) {}
+
+  ~FailureHttpFetcherTestDelegate() override {
+    if (server_) {
+      LOG(INFO) << "Stopping server in destructor";
+      delete server_;
+      LOG(INFO) << "server stopped";
+    }
+  }
+
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    if (server_) {
+      LOG(INFO) << "Stopping server in ReceivedBytes";
+      delete server_;
+      LOG(INFO) << "server stopped";
+      server_ = nullptr;
+    }
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_FALSE(successful);
+    EXPECT_EQ(0, fetcher->http_response_code());
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  PythonHttpServer* server_;
+};
+}  // namespace
+
+
+TYPED_TEST(HttpFetcherTest, FailureTest) {
+  // This test ensures that a fetcher responds correctly when a server isn't
+  // available at all.
+  if (this->test_.IsMock())
+    return;
+  {
+    FailureHttpFetcherTestDelegate delegate(nullptr);
+    unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    this->loop_.PostTask(FROM_HERE,
+                         base::Bind(StartTransfer,
+                                    fetcher.get(),
+                                    "http://host_doesnt_exist99999999"));
+    this->loop_.Run();
+
+    // Exiting and testing happens in the delegate
+  }
+}
+
+TYPED_TEST(HttpFetcherTest, ServerDiesTest) {
+  // This test starts a new http server and kills it after receiving its first
+  // set of bytes. It test whether or not our fetcher eventually gives up on
+  // retries and aborts correctly.
+  if (this->test_.IsMock())
+    return;
+  {
+    PythonHttpServer* server = new PythonHttpServer();
+    int port = server->GetPort();
+    ASSERT_TRUE(server->started_);
+
+    // Handles destruction and claims ownership.
+    FailureHttpFetcherTestDelegate delegate(server);
+    unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    this->loop_.PostTask(FROM_HERE, base::Bind(
+        StartTransfer,
+        fetcher.get(),
+        LocalServerUrlForPath(port,
+                              base::StringPrintf("/flaky/%d/%d/%d/%d",
+                                                 kBigLength,
+                                                 kFlakyTruncateLength,
+                                                 kFlakySleepEvery,
+                                                 kFlakySleepSecs))));
+    this->loop_.Run();
+
+    // Exiting and testing happens in the delegate
+  }
+}
+
+namespace {
+const HttpResponseCode kRedirectCodes[] = {
+  kHttpResponseMovedPermanently, kHttpResponseFound, kHttpResponseSeeOther,
+  kHttpResponseTempRedirect
+};
+
+class RedirectHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  explicit RedirectHttpFetcherTestDelegate(bool expected_successful)
+      : expected_successful_(expected_successful) {}
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    data.append(reinterpret_cast<const char*>(bytes), length);
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_EQ(expected_successful_, successful);
+    if (expected_successful_) {
+      EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+    } else {
+      EXPECT_GE(fetcher->http_response_code(), kHttpResponseMovedPermanently);
+      EXPECT_LE(fetcher->http_response_code(), kHttpResponseTempRedirect);
+    }
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+  bool expected_successful_;
+  string data;
+};
+
+// RedirectTest takes ownership of |http_fetcher|.
+void RedirectTest(const HttpServer* server,
+                  bool expected_successful,
+                  const string& url,
+                  HttpFetcher* http_fetcher) {
+  RedirectHttpFetcherTestDelegate delegate(expected_successful);
+  unique_ptr<HttpFetcher> fetcher(http_fetcher);
+  fetcher->set_delegate(&delegate);
+
+  MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      LocalServerUrlForPath(server->GetPort(), url)));
+  MessageLoop::current()->Run();
+  if (expected_successful) {
+    // verify the data we get back
+    ASSERT_EQ(kMediumLength, delegate.data.size());
+    for (int i = 0; i < kMediumLength; i += 10) {
+      // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+      ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+    }
+  }
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleRedirectTest) {
+  if (this->test_.IsMock())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  for (size_t c = 0; c < arraysize(kRedirectCodes); ++c) {
+    const string url = base::StringPrintf("/redirect/%d/download/%d",
+                                          kRedirectCodes[c],
+                                          kMediumLength);
+    RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+  }
+}
+
+TYPED_TEST(HttpFetcherTest, MaxRedirectTest) {
+  if (this->test_.IsMock())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  string url;
+  for (int r = 0; r < kDownloadMaxRedirects; r++) {
+    url += base::StringPrintf("/redirect/%d",
+                              kRedirectCodes[r % arraysize(kRedirectCodes)]);
+  }
+  url += base::StringPrintf("/download/%d", kMediumLength);
+  RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+}
+
+TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) {
+  if (this->test_.IsMock())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  string url;
+  for (int r = 0; r < kDownloadMaxRedirects + 1; r++) {
+    url += base::StringPrintf("/redirect/%d",
+                              kRedirectCodes[r % arraysize(kRedirectCodes)]);
+  }
+  url += base::StringPrintf("/download/%d", kMediumLength);
+  RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher());
+}
+
+namespace {
+class MultiHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  explicit MultiHttpFetcherTestDelegate(int expected_response_code)
+      : expected_response_code_(expected_response_code) {}
+
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    EXPECT_EQ(fetcher, fetcher_.get());
+    data.append(reinterpret_cast<const char*>(bytes), length);
+  }
+
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_EQ(fetcher, fetcher_.get());
+    EXPECT_EQ(expected_response_code_ != kHttpResponseUndefined, successful);
+    if (expected_response_code_ != 0)
+      EXPECT_EQ(expected_response_code_, fetcher->http_response_code());
+    // Destroy the fetcher (because we're allowed to).
+    fetcher_.reset(nullptr);
+    MessageLoop::current()->BreakLoop();
+  }
+
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+
+  unique_ptr<HttpFetcher> fetcher_;
+  int expected_response_code_;
+  string data;
+};
+
+void MultiTest(HttpFetcher* fetcher_in,
+               const string& url,
+               const vector<pair<off_t, off_t>>& ranges,
+               const string& expected_prefix,
+               off_t expected_size,
+               HttpResponseCode expected_response_code) {
+  MultiHttpFetcherTestDelegate delegate(expected_response_code);
+  delegate.fetcher_.reset(fetcher_in);
+
+  MultiRangeHttpFetcher* multi_fetcher =
+      dynamic_cast<MultiRangeHttpFetcher*>(fetcher_in);
+  ASSERT_TRUE(multi_fetcher);
+  multi_fetcher->ClearRanges();
+  for (vector<pair<off_t, off_t>>::const_iterator it = ranges.begin(),
+           e = ranges.end(); it != e; ++it) {
+    string tmp_str = base::StringPrintf("%jd+", it->first);
+    if (it->second > 0) {
+      base::StringAppendF(&tmp_str, "%jd", it->second);
+      multi_fetcher->AddRange(it->first, it->second);
+    } else {
+      base::StringAppendF(&tmp_str, "?");
+      multi_fetcher->AddRange(it->first);
+    }
+    LOG(INFO) << "added range: " << tmp_str;
+  }
+  dynamic_cast<FakeSystemState*>(fetcher_in->GetSystemState())
+      ->fake_hardware()->SetIsOfficialBuild(false);
+  multi_fetcher->set_delegate(&delegate);
+
+  MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(StartTransfer, multi_fetcher, url));
+  MessageLoop::current()->Run();
+
+  EXPECT_EQ(expected_size, delegate.data.size());
+  EXPECT_EQ(expected_prefix,
+            string(delegate.data.data(), expected_prefix.size()));
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherSimpleTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 25));
+  ranges.push_back(make_pair(99, 0));
+  MultiTest(this->test_.NewLargeFetcher(),
+            this->test_.BigUrl(server->GetPort()),
+            ranges,
+            "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+            kBigLength - (99 - 25),
+            kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherLengthLimitTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 24));
+  MultiTest(this->test_.NewLargeFetcher(),
+            this->test_.BigUrl(server->GetPort()),
+            ranges,
+            "abcdefghijabcdefghijabcd",
+            24,
+            kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherMultiEndTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(kBigLength - 2, 0));
+  ranges.push_back(make_pair(kBigLength - 3, 0));
+  MultiTest(this->test_.NewLargeFetcher(),
+            this->test_.BigUrl(server->GetPort()),
+            ranges,
+            "ijhij",
+            5,
+            kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherInsufficientTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(kBigLength - 2, 4));
+  for (int i = 0; i < 2; ++i) {
+    LOG(INFO) << "i = " << i;
+    MultiTest(this->test_.NewLargeFetcher(),
+              this->test_.BigUrl(server->GetPort()),
+              ranges,
+              "ij",
+              2,
+              kHttpResponseUndefined);
+    ranges.push_back(make_pair(0, 5));
+  }
+}
+
+// Issue #18143: when a fetch of a secondary chunk out of a chain, then it
+// should retry with other proxies listed before giving up.
+//
+// (1) successful recovery: The offset fetch will fail twice but succeed with
+// the third proxy.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetRecoverableTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 25));
+  ranges.push_back(make_pair(99, 0));
+  MultiTest(this->test_.NewLargeFetcher(3),
+            LocalServerUrlForPath(server->GetPort(),
+                                  base::StringPrintf("/error-if-offset/%d/2",
+                                                     kBigLength)),
+            ranges,
+            "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+            kBigLength - (99 - 25),
+            kHttpResponsePartialContent);
+}
+
+// (2) unsuccessful recovery: The offset fetch will fail repeatedly.  The
+// fetcher will signal a (failed) completed transfer to the delegate.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetUnrecoverableTest) {
+  if (!this->test_.IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(this->test_.CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  vector<pair<off_t, off_t>> ranges;
+  ranges.push_back(make_pair(0, 25));
+  ranges.push_back(make_pair(99, 0));
+  MultiTest(this->test_.NewLargeFetcher(2),
+            LocalServerUrlForPath(server->GetPort(),
+                                  base::StringPrintf("/error-if-offset/%d/3",
+                                                     kBigLength)),
+            ranges,
+            "abcdefghijabcdefghijabcde",  // only received the first chunk
+            25,
+            kHttpResponseUndefined);
+}
+
+
+
+namespace {
+class BlockedTransferTestDelegate : public HttpFetcherDelegate {
+ public:
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes, size_t length) override {
+    ADD_FAILURE();
+  }
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+    EXPECT_FALSE(successful);
+    MessageLoop::current()->BreakLoop();
+  }
+  void TransferTerminated(HttpFetcher* fetcher) override {
+    ADD_FAILURE();
+  }
+};
+
+void BlockedTransferTestHelper(AnyHttpFetcherTest* fetcher_test,
+                               bool is_official_build) {
+  if (fetcher_test->IsMock() || fetcher_test->IsMulti())
+    return;
+
+  unique_ptr<HttpServer> server(fetcher_test->CreateServer());
+  ASSERT_TRUE(server->started_);
+
+  BlockedTransferTestDelegate delegate;
+  unique_ptr<HttpFetcher> fetcher(fetcher_test->NewLargeFetcher());
+  LOG(INFO) << "is_official_build: " << is_official_build;
+  // NewLargeFetcher creates the HttpFetcher* with a FakeSystemState.
+  dynamic_cast<FakeSystemState*>(fetcher->GetSystemState())
+      ->fake_hardware()->SetIsOfficialBuild(is_official_build);
+  fetcher->set_delegate(&delegate);
+
+  MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+      StartTransfer,
+      fetcher.get(),
+      LocalServerUrlForPath(server->GetPort(),
+                            fetcher_test->SmallUrl(server->GetPort()))));
+  MessageLoop::current()->Run();
+}
+}  // namespace
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferTest) {
+  BlockedTransferTestHelper(&this->test_, false);
+}
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferOfficialBuildTest) {
+  BlockedTransferTestHelper(&this->test_, true);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/hwid_override.cc b/hwid_override.cc
new file mode 100644
index 0000000..258a997
--- /dev/null
+++ b/hwid_override.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 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/hwid_override.h"
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <chromeos/key_value_store.h>
+
+using std::map;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char HwidOverride::kHwidOverrideKey[] = "HWID_OVERRIDE";
+
+HwidOverride::HwidOverride() {}
+
+HwidOverride::~HwidOverride() {}
+
+string HwidOverride::Read(const base::FilePath& root) {
+  chromeos::KeyValueStore lsb_release;
+  lsb_release.Load(base::FilePath(root.value() + "/etc/lsb-release"));
+  string result;
+  if (lsb_release.GetString(kHwidOverrideKey, &result))
+    return result;
+  return "";
+}
+
+}  // namespace chromeos_update_engine
diff --git a/hwid_override.h b/hwid_override.h
new file mode 100644
index 0000000..dc0873b
--- /dev/null
+++ b/hwid_override.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 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_HWID_OVERRIDE_H_
+#define UPDATE_ENGINE_HWID_OVERRIDE_H_
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// Class that allows HWID to be read from <root>/etc/lsb-release.
+class HwidOverride {
+ public:
+  HwidOverride();
+  ~HwidOverride();
+
+  // Read HWID from an /etc/lsb-release file under given root.
+  // An empty string is returned if there is any error.
+  static std::string Read(const base::FilePath& root);
+
+  static const char kHwidOverrideKey[];
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HwidOverride);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_HWID_OVERRIDE_H_
diff --git a/hwid_override_unittest.cc b/hwid_override_unittest.cc
new file mode 100644
index 0000000..44a114a
--- /dev/null
+++ b/hwid_override_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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/hwid_override.h"
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class HwidOverrideTest : public ::testing::Test {
+ public:
+  HwidOverrideTest() {}
+  ~HwidOverrideTest() override = default;
+
+  void SetUp() override {
+    ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+    ASSERT_TRUE(base::CreateDirectory(tempdir_.path().Append("etc")));
+  }
+
+ protected:
+  base::ScopedTempDir tempdir_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HwidOverrideTest);
+};
+
+TEST_F(HwidOverrideTest, ReadGood) {
+  std::string expected_hwid("expected");
+  std::string keyval(HwidOverride::kHwidOverrideKey);
+  keyval += ("=" + expected_hwid);
+  ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+                            keyval.c_str(), keyval.length()),
+            keyval.length());
+  EXPECT_EQ(expected_hwid, HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadNothing) {
+  std::string keyval("SOMETHING_ELSE=UNINTERESTING");
+  ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+                            keyval.c_str(), keyval.length()),
+            keyval.length());
+  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadFailure) {
+  EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/init/update-engine.conf b/init/update-engine.conf
new file mode 100644
index 0000000..35bfc06
--- /dev/null
+++ b/init/update-engine.conf
@@ -0,0 +1,27 @@
+# Copyright (c) 2012 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.
+
+description     "System software update service"
+author          "chromium-os-dev@chromium.org"
+
+# N.B. The chromeos-factoryinstall ebuild edits the 'start on' line so as
+# to disable update_engine in factory images.  Do not change this without
+# also updating that reference.
+start on starting system-services
+stop on stopping system-services
+respawn
+
+expect fork
+
+# Runs the daemon at low/idle IO priority so that updates don't
+# impact system responsiveness.
+exec ionice -c3 update_engine
+
+# Put update_engine process in its own cgroup.
+# Default cpu.shares is 1024.
+post-start script
+  cgroup_dir="/sys/fs/cgroup/cpu/${UPSTART_JOB}"
+  mkdir -p "${cgroup_dir}"
+  echo $(status | cut -f 4 -d ' ') > "${cgroup_dir}/tasks"
+end script
diff --git a/install_plan.cc b/install_plan.cc
new file mode 100644
index 0000000..7b97876
--- /dev/null
+++ b/install_plan.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2013 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/install_plan.h"
+
+#include <base/logging.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+InstallPlan::InstallPlan(bool is_resume,
+                         bool is_full_update,
+                         const string& url,
+                         uint64_t payload_size,
+                         const string& payload_hash,
+                         uint64_t metadata_size,
+                         const string& metadata_signature,
+                         const string& install_path,
+                         const string& kernel_install_path,
+                         const string& source_path,
+                         const string& kernel_source_path,
+                         const string& public_key_rsa)
+    : is_resume(is_resume),
+      is_full_update(is_full_update),
+      download_url(url),
+      payload_size(payload_size),
+      payload_hash(payload_hash),
+      metadata_size(metadata_size),
+      metadata_signature(metadata_signature),
+      install_path(install_path),
+      kernel_install_path(kernel_install_path),
+      source_path(source_path),
+      kernel_source_path(kernel_source_path),
+      kernel_size(0),
+      rootfs_size(0),
+      hash_checks_mandatory(false),
+      powerwash_required(false),
+      public_key_rsa(public_key_rsa) {}
+
+InstallPlan::InstallPlan() : is_resume(false),
+                             is_full_update(false),  // play it safe.
+                             payload_size(0),
+                             metadata_size(0),
+                             kernel_size(0),
+                             rootfs_size(0),
+                             hash_checks_mandatory(false),
+                             powerwash_required(false) {}
+
+
+bool InstallPlan::operator==(const InstallPlan& that) const {
+  return ((is_resume == that.is_resume) &&
+          (is_full_update == that.is_full_update) &&
+          (download_url == that.download_url) &&
+          (payload_size == that.payload_size) &&
+          (payload_hash == that.payload_hash) &&
+          (metadata_size == that.metadata_size) &&
+          (metadata_signature == that.metadata_signature) &&
+          (install_path == that.install_path) &&
+          (kernel_install_path == that.kernel_install_path) &&
+          (source_path == that.source_path) &&
+          (kernel_source_path == that.kernel_source_path));
+}
+
+bool InstallPlan::operator!=(const InstallPlan& that) const {
+  return !((*this) == that);
+}
+
+void InstallPlan::Dump() const {
+  LOG(INFO) << "InstallPlan: "
+            << (is_resume ? "resume" : "new_update")
+            << ", payload type: " << (is_full_update ? "full" : "delta")
+            << ", url: " << download_url
+            << ", payload size: " << payload_size
+            << ", payload hash: " << payload_hash
+            << ", metadata size: " << metadata_size
+            << ", metadata signature: " << metadata_signature
+            << ", install_path: " << install_path
+            << ", kernel_install_path: " << kernel_install_path
+            << ", source_path: " << source_path
+            << ", kernel_source_path: " << kernel_source_path
+            << ", hash_checks_mandatory: " << utils::ToString(
+                hash_checks_mandatory)
+            << ", powerwash_required: " << utils::ToString(
+                powerwash_required);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/install_plan.h b/install_plan.h
new file mode 100644
index 0000000..756822a
--- /dev/null
+++ b/install_plan.h
@@ -0,0 +1,131 @@
+// Copyright (c) 2011 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_INSTALL_PLAN_H_
+#define UPDATE_ENGINE_INSTALL_PLAN_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/action.h"
+
+// InstallPlan is a simple struct that contains relevant info for many
+// parts of the update system about the install that should happen.
+namespace chromeos_update_engine {
+
+struct InstallPlan {
+  InstallPlan(bool is_resume,
+              bool is_full_update,
+              const std::string& url,
+              uint64_t payload_size,
+              const std::string& payload_hash,
+              uint64_t metadata_size,
+              const std::string& metadata_signature,
+              const std::string& install_path,
+              const std::string& kernel_install_path,
+              const std::string& source_path,
+              const std::string& kernel_source_path,
+              const std::string& public_key_rsa);
+
+  // Default constructor: Initialize all members which don't have a class
+  // initializer.
+  InstallPlan();
+
+  bool operator==(const InstallPlan& that) const;
+  bool operator!=(const InstallPlan& that) const;
+
+  void Dump() const;
+
+  bool is_resume;
+  bool is_full_update;
+  std::string download_url;  // url to download from
+  std::string version;       // version we are installing.
+
+  uint64_t payload_size;                 // size of the payload
+  std::string payload_hash;             // SHA256 hash of the payload
+  uint64_t metadata_size;                // size of the metadata
+  std::string metadata_signature;        // signature of the  metadata
+  std::string install_path;              // path to install device
+  std::string kernel_install_path;       // path to kernel install device
+  std::string source_path;               // path to source device
+  std::string kernel_source_path;        // path to source kernel device
+
+  // The fields below are used for kernel and rootfs verification. The flow is:
+  //
+  // 1. FilesystemVerifierAction computes and fills in the source partition
+  // sizes and hashes.
+  //
+  // 2. DownloadAction verifies the source partition sizes and hashes against
+  // the expected values transmitted in the update manifest. It fills in the
+  // expected applied partition sizes and hashes based on the manifest.
+  //
+  // 3. FilesystemVerifierAction computes and verifies the applied and source
+  // partition sizes and hashes against the expected values.
+  uint64_t kernel_size;
+  uint64_t rootfs_size;
+  chromeos::Blob kernel_hash;
+  chromeos::Blob rootfs_hash;
+  chromeos::Blob source_kernel_hash;
+  chromeos::Blob source_rootfs_hash;
+
+  // True if payload hash checks are mandatory based on the system state and
+  // the Omaha response.
+  bool hash_checks_mandatory;
+
+  // True if Powerwash is required on reboot after applying the payload.
+  // False otherwise.
+  bool powerwash_required;
+
+  // If not blank, a base-64 encoded representation of the PEM-encoded
+  // public key in the response.
+  std::string public_key_rsa;
+};
+
+class InstallPlanAction;
+
+template<>
+class ActionTraits<InstallPlanAction> {
+ public:
+  // Takes the install plan as input
+  typedef InstallPlan InputObjectType;
+  // Passes the install plan as output
+  typedef InstallPlan OutputObjectType;
+};
+
+// Basic action that only receives and sends Install Plans.
+// Can be used to construct an Install Plan to send to any other Action that
+// accept an InstallPlan.
+class InstallPlanAction : public Action<InstallPlanAction> {
+ public:
+  InstallPlanAction() {}
+  explicit InstallPlanAction(const InstallPlan& install_plan):
+    install_plan_(install_plan) {}
+
+  void PerformAction() override {
+    if (HasOutputPipe()) {
+      SetOutputObject(install_plan_);
+    }
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+
+  InstallPlan* install_plan() { return &install_plan_; }
+
+  static std::string StaticType() { return "InstallPlanAction"; }
+  std::string Type() const override { return StaticType(); }
+
+  typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;
+
+ private:
+  InstallPlan install_plan_;
+
+  DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_INSTALL_PLAN_H_
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
new file mode 100644
index 0000000..9d32293
--- /dev/null
+++ b/libcurl_http_fetcher.cc
@@ -0,0 +1,560 @@
+// Copyright (c) 2009 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/libcurl_http_fetcher.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/hardware_interface.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::max;
+using std::string;
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+namespace {
+const int kNoNetworkRetrySeconds = 10;
+const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates";
+}  // namespace
+
+LibcurlHttpFetcher::~LibcurlHttpFetcher() {
+  LOG_IF(ERROR, transfer_in_progress_)
+      << "Destroying the fetcher while a transfer is in progress.";
+  CleanUp();
+}
+
+bool LibcurlHttpFetcher::GetProxyType(const string& proxy,
+                                      curl_proxytype* out_type) {
+  if (base::StartsWithASCII(proxy, "socks5://", true) ||
+      base::StartsWithASCII(proxy, "socks://", true)) {
+    *out_type = CURLPROXY_SOCKS5_HOSTNAME;
+    return true;
+  }
+  if (base::StartsWithASCII(proxy, "socks4://", true)) {
+    *out_type = CURLPROXY_SOCKS4A;
+    return true;
+  }
+  if (base::StartsWithASCII(proxy, "http://", true) ||
+      base::StartsWithASCII(proxy, "https://", true)) {
+    *out_type = CURLPROXY_HTTP;
+    return true;
+  }
+  if (base::StartsWithASCII(proxy, kNoProxy, true)) {
+    // known failure case. don't log.
+    return false;
+  }
+  LOG(INFO) << "Unknown proxy type: " << proxy;
+  return false;
+}
+
+void LibcurlHttpFetcher::ResumeTransfer(const string& url) {
+  LOG(INFO) << "Starting/Resuming transfer";
+  CHECK(!transfer_in_progress_);
+  url_ = url;
+  curl_multi_handle_ = curl_multi_init();
+  CHECK(curl_multi_handle_);
+
+  curl_handle_ = curl_easy_init();
+  CHECK(curl_handle_);
+
+  CHECK(HasProxy());
+  bool is_direct = (GetCurrentProxy() == kNoProxy);
+  LOG(INFO) << "Using proxy: " << (is_direct ? "no" : "yes");
+  if (is_direct) {
+    CHECK_EQ(curl_easy_setopt(curl_handle_,
+                              CURLOPT_PROXY,
+                              ""), CURLE_OK);
+  } else {
+    CHECK_EQ(curl_easy_setopt(curl_handle_,
+                              CURLOPT_PROXY,
+                              GetCurrentProxy().c_str()), CURLE_OK);
+    // Curl seems to require us to set the protocol
+    curl_proxytype type;
+    if (GetProxyType(GetCurrentProxy(), &type)) {
+      CHECK_EQ(curl_easy_setopt(curl_handle_,
+                                CURLOPT_PROXYTYPE,
+                                type), CURLE_OK);
+    }
+  }
+
+  if (post_data_set_) {
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
+                              post_data_.data()),
+             CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
+                              post_data_.size()),
+             CURLE_OK);
+
+    // Set the Content-Type HTTP header, if one was specifically set.
+    CHECK(!curl_http_headers_);
+    if (post_content_type_ != kHttpContentTypeUnspecified) {
+      const string content_type_attr =
+        base::StringPrintf("Content-Type: %s",
+                           GetHttpContentTypeString(post_content_type_));
+      curl_http_headers_ = curl_slist_append(nullptr,
+                                             content_type_attr.c_str());
+      CHECK(curl_http_headers_);
+      CHECK_EQ(
+          curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER,
+                           curl_http_headers_),
+          CURLE_OK);
+    } else {
+      LOG(WARNING) << "no content type set, using libcurl default";
+    }
+  }
+
+  if (bytes_downloaded_ > 0 || download_length_) {
+    // Resume from where we left off.
+    resume_offset_ = bytes_downloaded_;
+    CHECK_GE(resume_offset_, 0);
+
+    // Compute end offset, if one is specified. As per HTTP specification, this
+    // is an inclusive boundary. Make sure it doesn't overflow.
+    size_t end_offset = 0;
+    if (download_length_) {
+      end_offset = static_cast<size_t>(resume_offset_) + download_length_ - 1;
+      CHECK_LE((size_t) resume_offset_, end_offset);
+    }
+
+    // Create a string representation of the desired range.
+    string range_str = (end_offset ?
+                        base::StringPrintf("%jd-%zu", resume_offset_,
+                                           end_offset) :
+                        base::StringPrintf("%jd-", resume_offset_));
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_RANGE, range_str.c_str()),
+             CURLE_OK);
+  }
+
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
+                            StaticLibcurlWrite), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()),
+           CURLE_OK);
+
+  // If the connection drops under |low_speed_limit_bps_| (10
+  // bytes/sec by default) for |low_speed_time_seconds_| (90 seconds,
+  // 180 on non-official builds), reconnect.
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT,
+                            low_speed_limit_bps_),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_TIME,
+                            low_speed_time_seconds_),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CONNECTTIMEOUT,
+                            connect_timeout_seconds_),
+           CURLE_OK);
+
+  // By default, libcurl doesn't follow redirections. Allow up to
+  // |kDownloadMaxRedirects| redirections.
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_FOLLOWLOCATION, 1), CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_MAXREDIRS,
+                            kDownloadMaxRedirects),
+           CURLE_OK);
+
+  // Lock down the appropriate curl options for HTTP or HTTPS depending on
+  // the url.
+  if (GetSystemState()->hardware()->IsOfficialBuild()) {
+    if (base::StartsWithASCII(url_, "http://", false))
+      SetCurlOptionsForHttp();
+    else
+      SetCurlOptionsForHttps();
+  } else {
+    LOG(INFO) << "Not setting http(s) curl options because we are "
+              << "running a dev/test image";
+  }
+
+  CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
+  transfer_in_progress_ = true;
+}
+
+// Lock down only the protocol in case of HTTP.
+void LibcurlHttpFetcher::SetCurlOptionsForHttp() {
+  LOG(INFO) << "Setting up curl options for HTTP";
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTP),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+                            CURLPROTO_HTTP),
+           CURLE_OK);
+}
+
+// Security lock-down in official builds: makes sure that peer certificate
+// verification is enabled, restricts the set of trusted certificates,
+// restricts protocols to HTTPS, restricts ciphers to HIGH.
+void LibcurlHttpFetcher::SetCurlOptionsForHttps() {
+  LOG(INFO) << "Setting up curl options for HTTPS";
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_VERIFYPEER, 1),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CAPATH, kCACertificatesPath),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+                            CURLPROTO_HTTPS),
+           CURLE_OK);
+  CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CIPHER_LIST, "HIGH:!ADH"),
+           CURLE_OK);
+  if (check_certificate_ != CertificateChecker::kNone) {
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_DATA,
+                              &check_certificate_),
+             CURLE_OK);
+    CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_FUNCTION,
+                              CertificateChecker::ProcessSSLContext),
+             CURLE_OK);
+  }
+}
+
+
+// Begins the transfer, which must not have already been started.
+void LibcurlHttpFetcher::BeginTransfer(const string& url) {
+  CHECK(!transfer_in_progress_);
+  url_ = url;
+  auto closure = base::Bind(&LibcurlHttpFetcher::ProxiesResolved,
+                            base::Unretained(this));
+  if (!ResolveProxiesForUrl(url_, closure)) {
+    LOG(ERROR) << "Couldn't resolve proxies";
+    if (delegate_)
+      delegate_->TransferComplete(this, false);
+  }
+}
+
+void LibcurlHttpFetcher::ProxiesResolved() {
+  transfer_size_ = -1;
+  resume_offset_ = 0;
+  retry_count_ = 0;
+  no_network_retry_count_ = 0;
+  http_response_code_ = 0;
+  terminate_requested_ = false;
+  sent_byte_ = false;
+  ResumeTransfer(url_);
+  CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::ForceTransferTermination() {
+  CleanUp();
+  if (delegate_) {
+    // Note that after the callback returns this object may be destroyed.
+    delegate_->TransferTerminated(this);
+  }
+}
+
+void LibcurlHttpFetcher::TerminateTransfer() {
+  if (in_write_callback_) {
+    terminate_requested_ = true;
+  } else {
+    ForceTransferTermination();
+  }
+}
+
+void LibcurlHttpFetcher::CurlPerformOnce() {
+  CHECK(transfer_in_progress_);
+  int running_handles = 0;
+  CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
+
+  // libcurl may request that we immediately call curl_multi_perform after it
+  // returns, so we do. libcurl promises that curl_multi_perform will not block.
+  while (CURLM_CALL_MULTI_PERFORM == retcode) {
+    retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
+    if (terminate_requested_) {
+      ForceTransferTermination();
+      return;
+    }
+  }
+  if (0 == running_handles) {
+    GetHttpResponseCode();
+    if (http_response_code_) {
+      LOG(INFO) << "HTTP response code: " << http_response_code_;
+      no_network_retry_count_ = 0;
+    } else {
+      LOG(ERROR) << "Unable to get http response code.";
+    }
+
+    // we're done!
+    CleanUp();
+
+    // TODO(petkov): This temporary code tries to deal with the case where the
+    // update engine performs an update check while the network is not ready
+    // (e.g., right after resume). Longer term, we should check if the network
+    // is online/offline and return an appropriate error code.
+    if (!sent_byte_ &&
+        http_response_code_ == 0 &&
+        no_network_retry_count_ < no_network_max_retries_) {
+      no_network_retry_count_++;
+      MessageLoop::current()->PostDelayedTask(
+          FROM_HERE,
+          base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+                     base::Unretained(this)),
+          TimeDelta::FromSeconds(kNoNetworkRetrySeconds));
+      LOG(INFO) << "No HTTP response, retry " << no_network_retry_count_;
+      return;
+    }
+
+    if ((!sent_byte_ && !IsHttpResponseSuccess()) || IsHttpResponseError()) {
+      // The transfer completed w/ error and we didn't get any bytes.
+      // If we have another proxy to try, try that.
+      //
+      // TODO(garnold) in fact there are two separate cases here: one case is an
+      // other-than-success return code (including no return code) and no
+      // received bytes, which is necessary due to the way callbacks are
+      // currently processing error conditions;  the second is an explicit HTTP
+      // error code, where some data may have been received (as in the case of a
+      // semi-successful multi-chunk fetch).  This is a confusing behavior and
+      // should be unified into a complete, coherent interface.
+      LOG(INFO) << "Transfer resulted in an error (" << http_response_code_
+                << "), " << bytes_downloaded_ << " bytes downloaded";
+
+      PopProxy();  // Delete the proxy we just gave up on.
+
+      if (HasProxy()) {
+        // We have another proxy. Retry immediately.
+        LOG(INFO) << "Retrying with next proxy setting";
+        MessageLoop::current()->PostTask(
+            FROM_HERE,
+            base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+                       base::Unretained(this)));
+      } else {
+        // Out of proxies. Give up.
+        LOG(INFO) << "No further proxies, indicating transfer complete";
+        if (delegate_)
+          delegate_->TransferComplete(this, false);  // signal fail
+      }
+    } else if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
+      retry_count_++;
+      LOG(INFO) << "Transfer interrupted after downloading "
+                << bytes_downloaded_ << " of " << transfer_size_ << " bytes. "
+                << transfer_size_ - bytes_downloaded_ << " bytes remaining "
+                << "after " << retry_count_ << " attempt(s)";
+
+      if (retry_count_ > max_retry_count_) {
+        LOG(INFO) << "Reached max attempts (" << retry_count_ << ")";
+        if (delegate_)
+          delegate_->TransferComplete(this, false);  // signal fail
+      } else {
+        // Need to restart transfer
+        LOG(INFO) << "Restarting transfer to download the remaining bytes";
+        MessageLoop::current()->PostDelayedTask(
+            FROM_HERE,
+            base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+                       base::Unretained(this)),
+            TimeDelta::FromSeconds(retry_seconds_));
+      }
+    } else {
+      LOG(INFO) << "Transfer completed (" << http_response_code_
+                << "), " << bytes_downloaded_ << " bytes downloaded";
+      if (delegate_) {
+        bool success = IsHttpResponseSuccess();
+        delegate_->TransferComplete(this, success);
+      }
+    }
+  } else {
+    // set up callback
+    SetupMessageLoopSources();
+  }
+}
+
+size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
+  // Update HTTP response first.
+  GetHttpResponseCode();
+  const size_t payload_size = size * nmemb;
+
+  // Do nothing if no payload or HTTP response is an error.
+  if (payload_size == 0 || !IsHttpResponseSuccess()) {
+    LOG(INFO) << "HTTP response unsuccessful (" << http_response_code_
+              << ") or no payload (" << payload_size << "), nothing to do";
+    return 0;
+  }
+
+  sent_byte_ = true;
+  {
+    double transfer_size_double;
+    CHECK_EQ(curl_easy_getinfo(curl_handle_,
+                               CURLINFO_CONTENT_LENGTH_DOWNLOAD,
+                               &transfer_size_double), CURLE_OK);
+    off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
+    if (new_transfer_size > 0) {
+      transfer_size_ = resume_offset_ + new_transfer_size;
+    }
+  }
+  bytes_downloaded_ += payload_size;
+  in_write_callback_ = true;
+  if (delegate_)
+    delegate_->ReceivedBytes(this, ptr, payload_size);
+  in_write_callback_ = false;
+  return payload_size;
+}
+
+void LibcurlHttpFetcher::Pause() {
+  CHECK(curl_handle_);
+  CHECK(transfer_in_progress_);
+  CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
+}
+
+void LibcurlHttpFetcher::Unpause() {
+  CHECK(curl_handle_);
+  CHECK(transfer_in_progress_);
+  CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
+}
+
+// This method sets up callbacks with the MessageLoop.
+void LibcurlHttpFetcher::SetupMessageLoopSources() {
+  fd_set fd_read;
+  fd_set fd_write;
+  fd_set fd_exc;
+
+  FD_ZERO(&fd_read);
+  FD_ZERO(&fd_write);
+  FD_ZERO(&fd_exc);
+
+  int fd_max = 0;
+
+  // Ask libcurl for the set of file descriptors we should track on its
+  // behalf.
+  CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
+                            &fd_exc, &fd_max), CURLM_OK);
+
+  // We should iterate through all file descriptors up to libcurl's fd_max or
+  // the highest one we're tracking, whichever is larger.
+  for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+    if (!fd_task_maps_[t].empty())
+      fd_max = max(fd_max, fd_task_maps_[t].rbegin()->first);
+  }
+
+  // For each fd, if we're not tracking it, track it. If we are tracking it, but
+  // libcurl doesn't care about it anymore, stop tracking it. After this loop,
+  // there should be exactly as many tasks scheduled in fd_task_maps_[0|1] as
+  // there are read/write fds that we're tracking.
+  for (int fd = 0; fd <= fd_max; ++fd) {
+    // Note that fd_exc is unused in the current version of libcurl so is_exc
+    // should always be false.
+    bool is_exc = FD_ISSET(fd, &fd_exc) != 0;
+    bool must_track[2] = {
+      is_exc || (FD_ISSET(fd, &fd_read) != 0),  // track 0 -- read
+      is_exc || (FD_ISSET(fd, &fd_write) != 0)  // track 1 -- write
+    };
+    MessageLoop::WatchMode watch_modes[2] = {
+      MessageLoop::WatchMode::kWatchRead,
+      MessageLoop::WatchMode::kWatchWrite,
+    };
+
+    for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+      auto fd_task_it = fd_task_maps_[t].find(fd);
+      bool tracked = fd_task_it != fd_task_maps_[t].end();
+
+      if (!must_track[t]) {
+        // If we have an outstanding io_channel, remove it.
+        if (tracked) {
+          MessageLoop::current()->CancelTask(fd_task_it->second);
+          fd_task_maps_[t].erase(fd_task_it);
+        }
+        continue;
+      }
+
+      // If we are already tracking this fd, continue -- nothing to do.
+      if (tracked)
+        continue;
+
+      // Track a new fd.
+      fd_task_maps_[t][fd] = MessageLoop::current()->WatchFileDescriptor(
+          FROM_HERE,
+          fd,
+          watch_modes[t],
+          true,  // persistent
+          base::Bind(&LibcurlHttpFetcher::CurlPerformOnce,
+                     base::Unretained(this)));
+
+      static int io_counter = 0;
+      io_counter++;
+      if (io_counter % 50 == 0) {
+        LOG(INFO) << "io_counter = " << io_counter;
+      }
+    }
+  }
+
+  // Set up a timeout callback for libcurl.
+  if (timeout_id_ == MessageLoop::kTaskIdNull) {
+    LOG(INFO) << "Setting up timeout source: " << idle_seconds_ << " seconds.";
+    timeout_id_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&LibcurlHttpFetcher::TimeoutCallback,
+                   base::Unretained(this)),
+        TimeDelta::FromSeconds(idle_seconds_));
+  }
+}
+
+void LibcurlHttpFetcher::RetryTimeoutCallback() {
+  ResumeTransfer(url_);
+  CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::TimeoutCallback() {
+  if (transfer_in_progress_)
+    CurlPerformOnce();
+
+  // We always re-schedule the callback, even if we don't want to be called
+  // anymore. We will remove the event source separately if we don't want to
+  // be called back.
+  timeout_id_ = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&LibcurlHttpFetcher::TimeoutCallback, base::Unretained(this)),
+      TimeDelta::FromSeconds(idle_seconds_));
+}
+
+void LibcurlHttpFetcher::CleanUp() {
+  MessageLoop::current()->CancelTask(timeout_id_);
+  timeout_id_ = MessageLoop::kTaskIdNull;
+
+  for (size_t t = 0; t < arraysize(fd_task_maps_); ++t) {
+    for (const auto& fd_taks_pair : fd_task_maps_[t]) {
+      if (!MessageLoop::current()->CancelTask(fd_taks_pair.second)) {
+        LOG(WARNING) << "Error canceling the watch task "
+                     << fd_taks_pair.second << " for "
+                     << (t ? "writing" : "reading") << " the fd "
+                     << fd_taks_pair.first;
+      }
+    }
+    fd_task_maps_[t].clear();
+  }
+
+  if (curl_http_headers_) {
+    curl_slist_free_all(curl_http_headers_);
+    curl_http_headers_ = nullptr;
+  }
+  if (curl_handle_) {
+    if (curl_multi_handle_) {
+      CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
+               CURLM_OK);
+    }
+    curl_easy_cleanup(curl_handle_);
+    curl_handle_ = nullptr;
+  }
+  if (curl_multi_handle_) {
+    CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
+    curl_multi_handle_ = nullptr;
+  }
+  transfer_in_progress_ = false;
+}
+
+void LibcurlHttpFetcher::GetHttpResponseCode() {
+  long http_response_code = 0;  // NOLINT(runtime/int) - curl needs long.
+  if (curl_easy_getinfo(curl_handle_,
+                        CURLINFO_RESPONSE_CODE,
+                        &http_response_code) == CURLE_OK) {
+    http_response_code_ = static_cast<int>(http_response_code);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h
new file mode 100644
index 0000000..4f68a9a
--- /dev/null
+++ b/libcurl_http_fetcher.h
@@ -0,0 +1,245 @@
+// Copyright (c) 2009 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_LIBCURL_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include <curl/curl.h>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/system_state.h"
+
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+class LibcurlHttpFetcher : public HttpFetcher {
+ public:
+  LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
+                     SystemState* system_state)
+      : HttpFetcher(proxy_resolver, system_state) {
+    // Dev users want a longer timeout (180 seconds) because they may
+    // be waiting on the dev server to build an image.
+    if (!system_state->hardware()->IsOfficialBuild())
+      low_speed_time_seconds_ = kDownloadDevModeLowSpeedTimeSeconds;
+    if (!system_state_->hardware()->IsOOBEComplete(nullptr))
+      max_retry_count_ = kDownloadMaxRetryCountOobeNotComplete;
+  }
+
+  // Cleans up all internal state. Does not notify delegate
+  ~LibcurlHttpFetcher() override;
+
+  void SetOffset(off_t offset) override { bytes_downloaded_ = offset; }
+
+  void SetLength(size_t length) override { download_length_ = length; }
+  void UnsetLength() override { SetLength(0); }
+
+  // Begins the transfer if it hasn't already begun.
+  void BeginTransfer(const std::string& url) override;
+
+  // If the transfer is in progress, aborts the transfer early. The transfer
+  // cannot be resumed.
+  void TerminateTransfer() override;
+
+  // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL).
+  void Pause() override;
+
+  // Resume the transfer by calling curl_easy_pause(CURLPAUSE_CONT).
+  void Unpause() override;
+
+  // Libcurl sometimes asks to be called back after some time while
+  // leaving that time unspecified. In that case, we pick a reasonable
+  // default of one second, but it can be overridden here. This is
+  // primarily useful for testing.
+  // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
+  //     if libcurl returns a -1 timeout here, it just means that libcurl
+  //     currently has no stored timeout value. You must not wait too long
+  //     (more than a few seconds perhaps) before you call
+  //     curl_multi_perform() again.
+  void set_idle_seconds(int seconds) override { idle_seconds_ = seconds; }
+
+  // Sets the retry timeout. Useful for testing.
+  void set_retry_seconds(int seconds) override { retry_seconds_ = seconds; }
+
+  void set_no_network_max_retries(int retries) {
+    no_network_max_retries_ = retries;
+  }
+
+  void set_check_certificate(
+      CertificateChecker::ServerToCheck check_certificate) {
+    check_certificate_ = check_certificate;
+  }
+
+  size_t GetBytesDownloaded() override {
+    return static_cast<size_t>(bytes_downloaded_);
+  }
+
+  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+    low_speed_limit_bps_ = low_speed_bps;
+    low_speed_time_seconds_ = low_speed_sec;
+  }
+
+  void set_connect_timeout(int connect_timeout_seconds) override {
+    connect_timeout_seconds_ = connect_timeout_seconds;
+  }
+
+  void set_max_retry_count(int max_retry_count) override {
+    max_retry_count_ = max_retry_count;
+  }
+
+ private:
+  // Callback for when proxy resolution has completed. This begins the
+  // transfer.
+  void ProxiesResolved();
+
+  // Asks libcurl for the http response code and stores it in the object.
+  void GetHttpResponseCode();
+
+  // Checks whether stored HTTP response is within the success range.
+  inline bool IsHttpResponseSuccess() {
+    return (http_response_code_ >= 200 && http_response_code_ < 300);
+  }
+
+  // Checks whether stored HTTP response is within the error range. This
+  // includes both errors with the request (4xx) and server errors (5xx).
+  inline bool IsHttpResponseError() {
+    return (http_response_code_ >= 400 && http_response_code_ < 600);
+  }
+
+  // Resumes a transfer where it left off. This will use the
+  // HTTP Range: header to make a new connection from where the last
+  // left off.
+  virtual void ResumeTransfer(const std::string& url);
+
+  void TimeoutCallback();
+  void RetryTimeoutCallback();
+
+  // Calls into curl_multi_perform to let libcurl do its work. Returns after
+  // curl_multi_perform is finished, which may actually be after more than
+  // one call to curl_multi_perform. This method will set up the glib run
+  // loop with sources for future work that libcurl will do.
+  // This method will not block.
+  // Returns true if we should resume immediately after this call.
+  void CurlPerformOnce();
+
+  // Sets up message loop sources as needed by libcurl. This is generally
+  // the file descriptor of the socket and a timer in case nothing happens
+  // on the fds.
+  void SetupMessageLoopSources();
+
+  // Callback called by libcurl when new data has arrived on the transfer
+  size_t LibcurlWrite(void *ptr, size_t size, size_t nmemb);
+  static size_t StaticLibcurlWrite(void *ptr, size_t size,
+                                   size_t nmemb, void *stream) {
+    return reinterpret_cast<LibcurlHttpFetcher*>(stream)->
+        LibcurlWrite(ptr, size, nmemb);
+  }
+
+  // Cleans up the following if they are non-null:
+  // curl(m) handles, fd_task_maps_, timeout_id_.
+  void CleanUp();
+
+  // Force terminate the transfer. This will invoke the delegate's (if any)
+  // TransferTerminated callback so, after returning, this fetcher instance may
+  // be destroyed.
+  void ForceTransferTermination();
+
+  // Sets the curl options for HTTP URL.
+  void SetCurlOptionsForHttp();
+
+  // Sets the curl options for HTTPS URL.
+  void SetCurlOptionsForHttps();
+
+  // Convert a proxy URL into a curl proxy type, if applicable. Returns true iff
+  // conversion was successful, false otherwise (in which case nothing is
+  // written to |out_type|).
+  bool GetProxyType(const std::string& proxy, curl_proxytype* out_type);
+
+  // Handles for the libcurl library
+  CURLM* curl_multi_handle_{nullptr};
+  CURL* curl_handle_{nullptr};
+  struct curl_slist* curl_http_headers_{nullptr};
+
+  // Lists of all read(0)/write(1) file descriptors that we're waiting on from
+  // the message loop. libcurl may open/close descriptors and switch their
+  // directions so maintain two separate lists so that watch conditions can be
+  // set appropriately.
+  std::map<int, chromeos::MessageLoop::TaskId> fd_task_maps_[2];
+
+  // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting
+  // on it.
+  chromeos::MessageLoop::TaskId timeout_id_{chromeos::MessageLoop::kTaskIdNull};
+
+  bool transfer_in_progress_{false};
+
+  // The transfer size. -1 if not known.
+  off_t transfer_size_{0};
+
+  // How many bytes have been downloaded and sent to the delegate.
+  off_t bytes_downloaded_{0};
+
+  // The remaining maximum number of bytes to download. Zero represents an
+  // unspecified length.
+  size_t download_length_{0};
+
+  // If we resumed an earlier transfer, data offset that we used for the
+  // new connection.  0 otherwise.
+  // In this class, resume refers to resuming a dropped HTTP connection,
+  // not to resuming an interrupted download.
+  off_t resume_offset_{0};
+
+  // Number of resumes performed so far and the max allowed.
+  int retry_count_{0};
+  int max_retry_count_{kDownloadMaxRetryCount};
+
+  // Seconds to wait before retrying a resume.
+  int retry_seconds_{20};
+
+  // Number of resumes due to no network (e.g., HTTP response code 0).
+  int no_network_retry_count_{0};
+  int no_network_max_retries_{0};
+
+  // Seconds to wait before asking libcurl to "perform".
+  int idle_seconds_{1};
+
+  // If true, we are currently performing a write callback on the delegate.
+  bool in_write_callback_{false};
+
+  // If true, we have returned at least one byte in the write callback
+  // to the delegate.
+  bool sent_byte_{false};
+
+  // We can't clean everything up while we're in a write callback, so
+  // if we get a terminate request, queue it until we can handle it.
+  bool terminate_requested_{false};
+
+  // Represents which server certificate to be checked against this
+  // connection's certificate. If no certificate check needs to be performed,
+  // this should be kNone.
+  CertificateChecker::ServerToCheck check_certificate_{
+      CertificateChecker::kNone};
+
+  int low_speed_limit_bps_{kDownloadLowSpeedLimitBps};
+  int low_speed_time_seconds_{kDownloadLowSpeedTimeSeconds};
+  int connect_timeout_seconds_{kDownloadConnectTimeoutSeconds};
+  int num_max_retries_;
+
+  DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
diff --git a/local_coverage_rate b/local_coverage_rate
new file mode 100755
index 0000000..8a44f73
--- /dev/null
+++ b/local_coverage_rate
@@ -0,0 +1,89 @@
+#!/bin/bash
+
+# Copyright (c) 2009 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.
+
+# Calculates the test-coverage percentage for non-test files in the
+# update_engine directory. Requires a file 'app.info' to contain the
+# results of running the unittests while collecting coverage data.
+
+cat app.info | awk -F '[,:]' '
+
+BEGIN { OFS = ":"; }
+
+/^SF:/{ FILEN = $2; }
+
+/^end_of_record$/{ FILEN = ""; }
+
+/^DA:/{ print FILEN, $2, $3; }
+
+' | sort | awk -F : '
+BEGIN {
+  OFS = ":";
+  FILEN = "";
+  LINE = "";
+  HITS = 0;
+}
+{
+  NEWFILEN = $1;
+  NEWLINE = $2;
+  if ((NEWFILEN == FILEN) && (NEWLINE == LINE)) {
+    HITS += $3
+  } else {
+    if (FILEN != "") {
+      print FILEN, LINE, HITS;
+    }
+    FILEN = NEWFILEN;
+    LINE = NEWLINE;
+    HITS = $3;
+  }
+}
+' | grep '^.*\/trunk\/src\/platform\/update_engine\/' | \
+fgrep -v '_unittest.cc:' | \
+fgrep -v '/test_utils.' | \
+fgrep -v '/test_http_server.cc' | \
+fgrep -v '/testrunner.cc' | \
+fgrep -v '/mock' | \
+fgrep -v '.pb.cc' | \
+awk -F : '
+
+function printfile() {
+  if (FNAME != "")
+    printf "%-40s %4d / %4d: %5.1f%%\n", FNAME, FILE_GOOD_LINES,
+        (FILE_BAD_LINES + FILE_GOOD_LINES),
+        (FILE_GOOD_LINES * 100) / (FILE_BAD_LINES + FILE_GOOD_LINES);
+}
+
+BEGIN {
+  FNAME = "";
+  FILE_BAD_LINES = 0;
+  FILE_GOOD_LINES = 0;
+}
+{
+  // calc filename
+  ARR_SIZE = split($1, PARTS, "/");
+  NEWFNAME = PARTS[ARR_SIZE];
+  if (NEWFNAME != FNAME) {
+    printfile();
+    FILE_BAD_LINES = 0;
+    FILE_GOOD_LINES = 0;
+    FNAME = NEWFNAME;
+  }
+  if ($3 == "0") {
+    BAD_LINES += 1;
+    FILE_BAD_LINES += 1;
+  } else {
+    GOOD_LINES += 1;
+    FILE_GOOD_LINES += 1;
+  }
+}
+
+END {
+  printfile();
+  print "---\nSummary: tested " GOOD_LINES " / " (BAD_LINES + GOOD_LINES);
+  printf(
+    "Test coverage: %.1f%%\n",
+    ((GOOD_LINES * 100) / (BAD_LINES + GOOD_LINES)));
+}
+'
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..51a04ec
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,253 @@
+// Copyright (c) 2012 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 <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/files/file_util.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/flag_helper.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <glib.h>
+#include <metrics/metrics_library.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/dbus_constants.h"
+#include "update_engine/dbus_service.h"
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/glib_utils.h"
+#include "update_engine/real_system_state.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+#include "update_engine/update_attempter.h"
+extern "C" {
+#include "update_engine/org.chromium.UpdateEngineInterface.dbusserver.h"
+}
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+const int kDBusSystemMaxWaitSeconds = 2 * 60;
+}  // namespace
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Wait for DBus to be ready by attempting to get the system bus up to
+// |timeout| time. Returns whether it succeeded to get the bus.
+bool WaitForDBusSystem(base::TimeDelta timeout) {
+  GError *error = nullptr;
+  DBusGConnection *bus = nullptr;
+  Clock clock;
+  base::Time deadline = clock.GetMonotonicTime() + timeout;
+
+  while (clock.GetMonotonicTime() < deadline) {
+    bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
+    if (bus)
+      return true;
+    LOG(WARNING) << "Failed to get system bus, waiting: "
+                 << utils::GetAndFreeGError(&error);
+    // Wait 1 second.
+    sleep(1);
+  }
+  LOG(ERROR) << "Failed to get system bus after " << timeout.InSeconds()
+             << " seconds.";
+  return false;
+}
+
+void SetupDBusService(UpdateEngineService* service) {
+  DBusGConnection *bus;
+  DBusGProxy *proxy;
+  GError *error = nullptr;
+
+  bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
+  LOG_IF(FATAL, !bus) << "Failed to get bus: "
+                      << utils::GetAndFreeGError(&error);
+  proxy = dbus_g_proxy_new_for_name(bus,
+                                    DBUS_SERVICE_DBUS,
+                                    DBUS_PATH_DBUS,
+                                    DBUS_INTERFACE_DBUS);
+  guint32 request_name_ret;
+  if (!org_freedesktop_DBus_request_name(proxy,
+                                         kUpdateEngineServiceName,
+                                         0,
+                                         &request_name_ret,
+                                         &error)) {
+    LOG(FATAL) << "Failed to get name: " << utils::GetAndFreeGError(&error);
+  }
+  if (request_name_ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+    g_warning("Got result code %u from requesting name", request_name_ret);
+    LOG(FATAL) << "Got result code " << request_name_ret
+               << " from requesting name, but expected "
+               << DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
+  }
+  dbus_g_connection_register_g_object(bus,
+                                      "/org/chromium/UpdateEngine",
+                                      G_OBJECT(service));
+}
+
+void SetupLogSymlink(const string& symlink_path, const string& log_path) {
+  // TODO(petkov): To ensure a smooth transition between non-timestamped and
+  // timestamped logs, move an existing log to start the first timestamped
+  // one. This code can go away once all clients are switched to this version or
+  // we stop caring about the old-style logs.
+  if (utils::FileExists(symlink_path.c_str()) &&
+      !utils::IsSymlink(symlink_path.c_str())) {
+    base::ReplaceFile(base::FilePath(symlink_path),
+                      base::FilePath(log_path),
+                      nullptr);
+  }
+  base::DeleteFile(base::FilePath(symlink_path), true);
+  if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) {
+    PLOG(ERROR) << "Unable to create symlink " << symlink_path
+                << " pointing at " << log_path;
+  }
+}
+
+string GetTimeAsString(time_t utime) {
+  struct tm tm;
+  CHECK_EQ(localtime_r(&utime, &tm), &tm);
+  char str[16];
+  CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u);
+  return str;
+}
+
+string SetupLogFile(const string& kLogsRoot) {
+  const string kLogSymlink = kLogsRoot + "/update_engine.log";
+  const string kLogsDir = kLogsRoot + "/update_engine";
+  const string kLogPath =
+      base::StringPrintf("%s/update_engine.%s",
+                         kLogsDir.c_str(),
+                         GetTimeAsString(::time(nullptr)).c_str());
+  mkdir(kLogsDir.c_str(), 0755);
+  SetupLogSymlink(kLogSymlink, kLogPath);
+  return kLogSymlink;
+}
+
+void SetupLogging(bool log_to_std_err) {
+  string log_file;
+  logging::LoggingSettings log_settings;
+  log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+  log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+
+  if (log_to_std_err) {
+    // Log to stderr initially.
+    log_settings.log_file = nullptr;
+    log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+  } else {
+    log_file = SetupLogFile("/var/log");
+    log_settings.log_file = log_file.c_str();
+    log_settings.logging_dest = logging::LOG_TO_FILE;
+  }
+
+  logging::InitLogging(log_settings);
+}
+
+}  // namespace
+}  // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+  DEFINE_bool(logtostderr, false,
+              "Write logs to stderr instead of to a file in log_dir.");
+  DEFINE_bool(foreground, false,
+              "Don't daemon()ize; run in foreground.");
+
+  ::g_type_init();
+  dbus_threads_init_default();
+  base::AtExitManager exit_manager;  // Required for base/rand_util.h.
+  chromeos_update_engine::Terminator::Init();
+  chromeos_update_engine::Subprocess::Init();
+  chromeos::FlagHelper::Init(argc, argv, "Chromium OS Update Engine");
+  chromeos_update_engine::SetupLogging(FLAGS_logtostderr);
+  if (!FLAGS_foreground)
+    PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed";
+
+  LOG(INFO) << "Chrome OS Update Engine starting";
+
+  // Ensure that all written files have safe permissions.
+  // This is a mask, so we _block_ execute for the owner, and ALL
+  // permissions for other users.
+  // Done _after_ log file creation.
+  umask(S_IXUSR | S_IRWXG | S_IRWXO);
+
+  // Create the single Glib main loop. Code accessing directly the glib main
+  // loop (such as calling g_timeout_add() or similar functions) will still work
+  // since the backend for the message loop is still the Glib main loop.
+  // TODO(deymo): Replace this |loop| with one based on libevent once no other
+  // code here uses glib directly.
+  chromeos::GlibMessageLoop loop;
+  loop.SetAsCurrent();
+
+  // Wait up to 2 minutes for DBus to be ready.
+  LOG_IF(FATAL, !chromeos_update_engine::WaitForDBusSystem(
+      base::TimeDelta::FromSeconds(kDBusSystemMaxWaitSeconds)))
+      << "Failed to initialize DBus, aborting.";
+
+  chromeos_update_engine::RealSystemState real_system_state;
+  LOG_IF(ERROR, !real_system_state.Initialize())
+      << "Failed to initialize system state.";
+  chromeos_update_engine::UpdateAttempter *update_attempter =
+      real_system_state.update_attempter();
+  CHECK(update_attempter);
+
+  // Sets static members for the certificate checker.
+  chromeos_update_engine::CertificateChecker::set_system_state(
+      &real_system_state);
+  chromeos_update_engine::OpenSSLWrapper openssl_wrapper;
+  chromeos_update_engine::CertificateChecker::set_openssl_wrapper(
+      &openssl_wrapper);
+
+  // Create the dbus service object:
+  dbus_g_object_type_install_info(UPDATE_ENGINE_TYPE_SERVICE,
+                                  &dbus_glib_update_engine_service_object_info);
+  UpdateEngineService* service = update_engine_service_new();
+  service->system_state_ = &real_system_state;
+  update_attempter->set_dbus_service(service);
+  chromeos_update_engine::SetupDBusService(service);
+
+  // Initiate update checks.
+  update_attempter->ScheduleUpdates();
+
+  // Update boot flags after 45 seconds.
+  loop.PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&chromeos_update_engine::UpdateAttempter::UpdateBootFlags,
+                 base::Unretained(update_attempter)),
+      base::TimeDelta::FromSeconds(45));
+
+  // Broadcast the update engine status on startup to ensure consistent system
+  // state on crashes.
+  loop.PostTask(FROM_HERE, base::Bind(
+      &chromeos_update_engine::UpdateAttempter::BroadcastStatus,
+      base::Unretained(update_attempter)));
+
+  // Run the UpdateEngineStarted() method on |update_attempter|.
+  loop.PostTask(FROM_HERE, base::Bind(
+      &chromeos_update_engine::UpdateAttempter::UpdateEngineStarted,
+      base::Unretained(update_attempter)));
+
+  // Run the main loop until exit time:
+  loop.Run();
+
+  // Cleanup:
+  update_attempter->set_dbus_service(nullptr);
+  g_object_unref(G_OBJECT(service));
+
+  loop.ReleaseFromCurrent();
+  LOG(INFO) << "Chrome OS Update Engine terminating";
+  return 0;
+}
diff --git a/metrics.cc b/metrics.cc
new file mode 100644
index 0000000..deb445b
--- /dev/null
+++ b/metrics.cc
@@ -0,0 +1,483 @@
+// Copyright 2014 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/metrics.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/constants.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+const char kMetricDailyOSAgeDays[] = "UpdateEngine.Daily.OSAgeDays";
+
+// UpdateEngine.Check.* metrics.
+const char kMetricCheckDownloadErrorCode[] =
+    "UpdateEngine.Check.DownloadErrorCode";
+const char kMetricCheckReaction[] = "UpdateEngine.Check.Reaction";
+const char kMetricCheckResult[] = "UpdateEngine.Check.Result";
+const char kMetricCheckTimeSinceLastCheckMinutes[] =
+    "UpdateEngine.Check.TimeSinceLastCheckMinutes";
+const char kMetricCheckTimeSinceLastCheckUptimeMinutes[] =
+    "UpdateEngine.Check.TimeSinceLastCheckUptimeMinutes";
+
+// UpdateEngine.Attempt.* metrics.
+const char kMetricAttemptNumber[] = "UpdateEngine.Attempt.Number";
+const char kMetricAttemptPayloadType[] =
+    "UpdateEngine.Attempt.PayloadType";
+const char kMetricAttemptPayloadSizeMiB[] =
+    "UpdateEngine.Attempt.PayloadSizeMiB";
+const char kMetricAttemptConnectionType[] =
+    "UpdateEngine.Attempt.ConnectionType";
+const char kMetricAttemptDurationMinutes[] =
+    "UpdateEngine.Attempt.DurationMinutes";
+const char kMetricAttemptDurationUptimeMinutes[] =
+    "UpdateEngine.Attempt.DurationUptimeMinutes";
+const char kMetricAttemptTimeSinceLastAttemptMinutes[] =
+    "UpdateEngine.Attempt.TimeSinceLastAttemptMinutes";
+const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[] =
+    "UpdateEngine.Attempt.TimeSinceLastAttemptUptimeMinutes";
+const char kMetricAttemptPayloadBytesDownloadedMiB[] =
+    "UpdateEngine.Attempt.PayloadBytesDownloadedMiB";
+const char kMetricAttemptPayloadDownloadSpeedKBps[] =
+    "UpdateEngine.Attempt.PayloadDownloadSpeedKBps";
+const char kMetricAttemptDownloadSource[] =
+    "UpdateEngine.Attempt.DownloadSource";
+const char kMetricAttemptResult[] =
+    "UpdateEngine.Attempt.Result";
+const char kMetricAttemptInternalErrorCode[] =
+    "UpdateEngine.Attempt.InternalErrorCode";
+const char kMetricAttemptDownloadErrorCode[] =
+    "UpdateEngine.Attempt.DownloadErrorCode";
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+const char kMetricSuccessfulUpdateAttemptCount[] =
+    "UpdateEngine.SuccessfulUpdate.AttemptCount";
+const char kMetricSuccessfulUpdateBytesDownloadedMiB[] =
+    "UpdateEngine.SuccessfulUpdate.BytesDownloadedMiB";
+const char kMetricSuccessfulUpdateDownloadOverheadPercentage[] =
+    "UpdateEngine.SuccessfulUpdate.DownloadOverheadPercentage";
+const char kMetricSuccessfulUpdateDownloadSourcesUsed[] =
+    "UpdateEngine.SuccessfulUpdate.DownloadSourcesUsed";
+const char kMetricSuccessfulUpdatePayloadType[] =
+    "UpdateEngine.SuccessfulUpdate.PayloadType";
+const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
+    "UpdateEngine.SuccessfulUpdate.PayloadSizeMiB";
+const char kMetricSuccessfulUpdateRebootCount[] =
+    "UpdateEngine.SuccessfulUpdate.RebootCount";
+const char kMetricSuccessfulUpdateTotalDurationMinutes[] =
+    "UpdateEngine.SuccessfulUpdate.TotalDurationMinutes";
+const char kMetricSuccessfulUpdateUpdatesAbandonedCount[] =
+    "UpdateEngine.SuccessfulUpdate.UpdatesAbandonedCount";
+const char kMetricSuccessfulUpdateUrlSwitchCount[] =
+    "UpdateEngine.SuccessfulUpdate.UrlSwitchCount";
+
+// UpdateEngine.Rollback.* metric.
+const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
+
+// UpdateEngine.* metrics.
+const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
+const char kMetricInstallDateProvisioningSource[] =
+    "UpdateEngine.InstallDateProvisioningSource";
+const char kMetricTimeToRebootMinutes[] =
+    "UpdateEngine.TimeToRebootMinutes";
+
+void ReportDailyMetrics(SystemState *system_state,
+                        base::TimeDelta os_age) {
+  string metric = metrics::kMetricDailyOSAgeDays;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(
+      metric,
+      static_cast<int>(os_age.InDays()),
+      0,     // min: 0 days
+      6*30,  // max: 6 months (approx)
+      50);   // num_buckets
+}
+
+void ReportUpdateCheckMetrics(SystemState *system_state,
+                              CheckResult result,
+                              CheckReaction reaction,
+                              DownloadErrorCode download_error_code) {
+  string metric;
+  int value;
+  int max_value;
+
+  if (result != metrics::CheckResult::kUnset) {
+    metric = metrics::kMetricCheckResult;
+    value = static_cast<int>(result);
+    max_value = static_cast<int>(metrics::CheckResult::kNumConstants) - 1;
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+    system_state->metrics_lib()->SendEnumToUMA(metric, value, max_value);
+  }
+  if (reaction != metrics::CheckReaction::kUnset) {
+    metric = metrics::kMetricCheckReaction;
+    value = static_cast<int>(reaction);
+    max_value = static_cast<int>(metrics::CheckReaction::kNumConstants) - 1;
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+    system_state->metrics_lib()->SendEnumToUMA(metric, value, max_value);
+  }
+  if (download_error_code != metrics::DownloadErrorCode::kUnset) {
+    metric = metrics::kMetricCheckDownloadErrorCode;
+    value = static_cast<int>(download_error_code);
+    LOG(INFO) << "Sending " << value << " for metric " << metric << " (sparse)";
+    system_state->metrics_lib()->SendSparseToUMA(metric, value);
+  }
+
+  base::TimeDelta time_since_last;
+  if (utils::WallclockDurationHelper(system_state,
+                                     kPrefsMetricsCheckLastReportingTime,
+                                     &time_since_last)) {
+    metric = kMetricCheckTimeSinceLastCheckMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+              << " for metric " << metric;
+    system_state->metrics_lib()->SendToUMA(
+        metric,
+        time_since_last.InMinutes(),
+        0,         // min: 0 min
+        30*24*60,  // max: 30 days
+        50);       // num_buckets
+  }
+
+  base::TimeDelta uptime_since_last;
+  static int64_t uptime_since_last_storage = 0;
+  if (utils::MonotonicDurationHelper(system_state,
+                                     &uptime_since_last_storage,
+                                     &uptime_since_last)) {
+    metric = kMetricCheckTimeSinceLastCheckUptimeMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+              << " for metric " << metric;
+    system_state->metrics_lib()->SendToUMA(
+        metric,
+        uptime_since_last.InMinutes(),
+        0,         // min: 0 min
+        30*24*60,  // max: 30 days
+        50);       // num_buckets
+  }
+}
+
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(
+    SystemState *system_state) {
+
+  string metric = metrics::kMetricAttemptResult;
+  AttemptResult attempt_result = AttemptResult::kAbnormalTermination;
+
+  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      static_cast<int>(attempt_result),
+      static_cast<int>(AttemptResult::kNumConstants));
+}
+
+void ReportUpdateAttemptMetrics(
+    SystemState *system_state,
+    int attempt_number,
+    PayloadType payload_type,
+    base::TimeDelta duration,
+    base::TimeDelta duration_uptime,
+    int64_t payload_size,
+    int64_t payload_bytes_downloaded,
+    int64_t payload_download_speed_bps,
+    DownloadSource download_source,
+    AttemptResult attempt_result,
+    ErrorCode internal_error_code,
+    DownloadErrorCode payload_download_error_code,
+    ConnectionType connection_type) {
+  string metric;
+
+  metric = metrics::kMetricAttemptNumber;
+  LOG(INFO) << "Uploading " << attempt_number << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         attempt_number,
+                                         0,    // min: 0 attempts
+                                         49,   // max: 49 attempts
+                                         50);  // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadType;
+  LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(metric,
+                                             payload_type,
+                                             kNumPayloadTypes);
+
+  metric = metrics::kMetricAttemptDurationMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         duration.InMinutes(),
+                                         0,         // min: 0 min
+                                         10*24*60,  // max: 10 days
+                                         50);       // num_buckets
+
+  metric = metrics::kMetricAttemptDurationUptimeMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         duration_uptime.InMinutes(),
+                                         0,         // min: 0 min
+                                         10*24*60,  // max: 10 days
+                                         50);       // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadSizeMiB;
+  int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << payload_size_mib << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         payload_size_mib,
+                                         0,     // min: 0 MiB
+                                         1024,  // max: 1024 MiB = 1 GiB
+                                         50);   // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
+  int64_t payload_bytes_downloaded_mib =
+       payload_bytes_downloaded / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         payload_bytes_downloaded_mib,
+                                         0,     // min: 0 MiB
+                                         1024,  // max: 1024 MiB = 1 GiB
+                                         50);   // num_buckets
+
+  metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps;
+  int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000;
+  LOG(INFO) << "Uploading " << payload_download_speed_kbps
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         payload_download_speed_kbps,
+                                         0,        // min: 0 kB/s
+                                         10*1000,  // max: 10000 kB/s = 10 MB/s
+                                         50);      // num_buckets
+
+  metric = metrics::kMetricAttemptDownloadSource;
+  LOG(INFO) << "Uploading " << download_source
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(metric,
+                                             download_source,
+                                             kNumDownloadSources);
+
+  metric = metrics::kMetricAttemptResult;
+  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      static_cast<int>(attempt_result),
+      static_cast<int>(AttemptResult::kNumConstants));
+
+  if (internal_error_code != ErrorCode::kSuccess) {
+    metric = metrics::kMetricAttemptInternalErrorCode;
+    LOG(INFO) << "Uploading " << internal_error_code
+              << " for metric " <<  metric;
+    system_state->metrics_lib()->SendEnumToUMA(
+        metric,
+        static_cast<int>(internal_error_code),
+        static_cast<int>(ErrorCode::kUmaReportedMax));
+  }
+
+  if (payload_download_error_code != DownloadErrorCode::kUnset) {
+    metric = metrics::kMetricAttemptDownloadErrorCode;
+    LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code)
+              << " for metric " <<  metric << " (sparse)";
+    system_state->metrics_lib()->SendSparseToUMA(
+        metric,
+        static_cast<int>(payload_download_error_code));
+  }
+
+  base::TimeDelta time_since_last;
+  if (utils::WallclockDurationHelper(system_state,
+                                     kPrefsMetricsAttemptLastReportingTime,
+                                     &time_since_last)) {
+    metric = kMetricAttemptTimeSinceLastAttemptMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+              << " for metric " << metric;
+    system_state->metrics_lib()->SendToUMA(
+        metric,
+        time_since_last.InMinutes(),
+        0,         // min: 0 min
+        30*24*60,  // max: 30 days
+        50);       // num_buckets
+  }
+
+  static int64_t uptime_since_last_storage = 0;
+  base::TimeDelta uptime_since_last;
+  if (utils::MonotonicDurationHelper(system_state,
+                                     &uptime_since_last_storage,
+                                     &uptime_since_last)) {
+    metric = kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
+    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+              << " for metric " << metric;
+    system_state->metrics_lib()->SendToUMA(
+        metric,
+        uptime_since_last.InMinutes(),
+        0,         // min: 0 min
+        30*24*60,  // max: 30 days
+        50);       // num_buckets
+  }
+
+  metric = metrics::kMetricAttemptConnectionType;
+  LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      static_cast<int>(connection_type),
+      static_cast<int>(ConnectionType::kNumConstants));
+}
+
+
+void ReportSuccessfulUpdateMetrics(
+         SystemState *system_state,
+         int attempt_count,
+         int updates_abandoned_count,
+         PayloadType payload_type,
+         int64_t payload_size,
+         int64_t num_bytes_downloaded[kNumDownloadSources],
+         int download_overhead_percentage,
+         base::TimeDelta total_duration,
+         int reboot_count,
+         int url_switch_count) {
+  string metric;
+  int64_t mbs;
+
+  metric = kMetricSuccessfulUpdatePayloadSizeMiB;
+  mbs = payload_size / kNumBytesInOneMiB;
+  LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         mbs,
+                                         0,     // min: 0 MiB
+                                         1024,  // max: 1024 MiB = 1 GiB
+                                         50);   // num_buckets
+
+  int64_t total_bytes = 0;
+  int download_sources_used = 0;
+  for (int i = 0; i < kNumDownloadSources + 1; i++) {
+    DownloadSource source = static_cast<DownloadSource>(i);
+
+    // Only consider this download source (and send byte counts) as
+    // having been used if we downloaded a non-trivial amount of bytes
+    // (e.g. at least 1 MiB) that contributed to the
+    // update. Otherwise we're going to end up with a lot of zero-byte
+    // events in the histogram.
+
+    metric = metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
+    if (i < kNumDownloadSources) {
+      metric += utils::ToString(source);
+      mbs = num_bytes_downloaded[i] / kNumBytesInOneMiB;
+      total_bytes += num_bytes_downloaded[i];
+      if (mbs > 0)
+        download_sources_used |= (1 << i);
+    } else {
+      mbs = total_bytes / kNumBytesInOneMiB;
+    }
+
+    if (mbs > 0) {
+      LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+      system_state->metrics_lib()->SendToUMA(metric,
+                                             mbs,
+                                             0,     // min: 0 MiB
+                                             1024,  // max: 1024 MiB = 1 GiB
+                                             50);   // num_buckets
+    }
+  }
+
+  metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
+  LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
+            << " (bit flags) for metric " << metric;
+  system_state->metrics_lib()->SendToUMA(
+      metric,
+      download_sources_used,
+      0,                               // min
+      (1 << kNumDownloadSources) - 1,  // max
+      1 << kNumDownloadSources);       // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
+  LOG(INFO) << "Uploading " << download_overhead_percentage
+            << "% for metric " << metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         download_overhead_percentage,
+                                         0,     // min: 0% overhead
+                                         1000,  // max: 1000% overhead
+                                         50);   // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
+  LOG(INFO) << "Uploading " << url_switch_count
+            << " (count) for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         url_switch_count,
+                                         0,    // min: 0 URL switches
+                                         49,   // max: 49 URL switches
+                                         50);  // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration)
+            << " for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(
+       metric,
+       static_cast<int>(total_duration.InMinutes()),
+       0,          // min: 0 min
+       365*24*60,  // max: 365 days ~= 1 year
+       50);        // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdateRebootCount;
+  LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
+            <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         reboot_count,
+                                         0,    // min: 0 reboots
+                                         49,   // max: 49 reboots
+                                         50);  // num_buckets
+
+  metric = metrics::kMetricSuccessfulUpdatePayloadType;
+  system_state->metrics_lib()->SendEnumToUMA(metric,
+                                             payload_type,
+                                             kNumPayloadTypes);
+  LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+            << " for metric " <<  metric;
+
+  metric = metrics::kMetricSuccessfulUpdateAttemptCount;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         attempt_count,
+                                         1,    // min: 1 attempt
+                                         50,   // max: 50 attempts
+                                         50);  // num_buckets
+  LOG(INFO) << "Uploading " << attempt_count
+            << " for metric " <<  metric;
+
+  metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount;
+  LOG(INFO) << "Uploading " << updates_abandoned_count
+            << " (count) for metric " <<  metric;
+  system_state->metrics_lib()->SendToUMA(metric,
+                                         updates_abandoned_count,
+                                         0,    // min: 0 counts
+                                         49,   // max: 49 counts
+                                         50);  // num_buckets
+}
+
+void ReportRollbackMetrics(SystemState *system_state,
+                           RollbackResult result) {
+  string metric;
+  int value;
+
+  metric = metrics::kMetricRollbackResult;
+  value = static_cast<int>(result);
+  LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric,
+      value,
+      static_cast<int>(metrics::RollbackResult::kNumConstants));
+}
+
+}  // namespace metrics
+
+}  // namespace chromeos_update_engine
diff --git a/metrics.h b/metrics.h
new file mode 100644
index 0000000..7dfd918
--- /dev/null
+++ b/metrics.h
@@ -0,0 +1,298 @@
+// Copyright 2014 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_METRICS_H_
+#define UPDATE_ENGINE_METRICS_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/error_code.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+extern const char kMetricDailyOSAgeDays[];
+
+// UpdateEngine.Check.* metrics.
+extern const char kMetricCheckDownloadErrorCode[];
+extern const char kMetricCheckReaction[];
+extern const char kMetricCheckResult[];
+extern const char kMetricCheckTimeSinceLastCheckMinutes[];
+extern const char kMetricCheckTimeSinceLastCheckUptimeMinutes[];
+
+// UpdateEngine.Attempt.* metrics.
+extern const char kMetricAttemptNumber[];
+extern const char kMetricAttemptPayloadType[];
+extern const char kMetricAttemptPayloadSizeMiB[];
+extern const char kMetricAttemptConnectionType[];
+extern const char kMetricAttemptDurationMinutes[];
+extern const char kMetricAttemptDurationUptimeMinutes[];
+extern const char kMetricAttemptTimeSinceLastAttemptSeconds[];
+extern const char kMetricAttemptTimeSinceLastAttemptUptimeSeconds[];
+extern const char kMetricAttemptPayloadBytesDownloaded[];
+extern const char kMetricAttemptPayloadDownloadSpeedKBps[];
+extern const char kMetricAttemptDownloadSource[];
+extern const char kMetricAttemptResult[];
+extern const char kMetricAttemptInternalErrorCode[];
+extern const char kMetricAttemptDownloadErrorCode[];
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+extern const char kMetricSuccessfulUpdateAttemptCount[];
+extern const char kMetricSuccessfulUpdateBytesDownloadedMiB[];
+extern const char kMetricSuccessfulUpdateDownloadOverheadPercentage[];
+extern const char kMetricSuccessfulUpdateDownloadSourcesUsed[];
+extern const char kMetricSuccessfulUpdatePayloadType[];
+extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
+extern const char kMetricSuccessfulUpdateTotalDurationMinutes[];
+extern const char kMetricSuccessfulUpdateRebootCount[];
+extern const char kMetricSuccessfulUpdateUpdatesAbandonedCount[];
+extern const char kMetricSuccessfulUpdateUrlSwitchCount[];
+
+// UpdateEngine.Rollback.* metric.
+extern const char kMetricRollbackResult[];
+
+// UpdateEngine.* metrics.
+extern const char kMetricFailedUpdateCount[];
+extern const char kMetricInstallDateProvisioningSource[];
+extern const char kMetricTimeToRebootMinutes[];
+
+// The possible outcomes when checking for updates.
+//
+// This is used in the UpdateEngine.Check.Result histogram.
+enum class CheckResult {
+  kUpdateAvailable,    // Response indicates an update is available.
+  kNoUpdateAvailable,  // Response indicates no updates are available.
+  kDownloadError,      // Error downloading response from Omaha.
+  kParsingError,       // Error parsing response.
+  kRebootPending,      // No update check was performed a reboot is pending.
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// Possible ways a device can react to a new update being available.
+//
+// This is used in the UpdateEngine.Check.Reaction histogram.
+enum class CheckReaction {
+  kUpdating,    // Device proceeds to download and apply update.
+  kIgnored  ,   // Device-policy dictates ignoring the update.
+  kDeferring,   // Device-policy dictates waiting.
+  kBackingOff,  // Previous errors dictates waiting.
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// The possible ways that downloading from a HTTP or HTTPS server can fail.
+//
+// This is used in the UpdateEngine.Check.DownloadErrorCode and
+// UpdateEngine.Attempt.DownloadErrorCode histograms.
+enum class DownloadErrorCode {
+  // Errors that can happen in the field. See http://crbug.com/355745
+  // for how we plan to add more detail in the future.
+  kDownloadError = 0,  // Error downloading data from server.
+
+  // IMPORTANT: When adding a new error code, add at the bottom of the
+  // above block and before the kInputMalformed field. This
+  // is to ensure that error codes are not reordered.
+
+  // This error code is used to convey that malformed input was given
+  // to the utils::GetDownloadErrorCode() function. This should never
+  // happen but if it does it's because of an internal update_engine
+  // error and we're interested in knowing this.
+  kInputMalformed = 100,
+
+  // Bucket for capturing HTTP status codes not in the 200-599
+  // range. This should never happen in practice but if it does we
+  // want to know.
+  kHttpStatusOther = 101,
+
+  // Above 200 and below 600, the value is the HTTP status code.
+  kHttpStatus200 = 200,
+
+  kNumConstants = 600,
+
+  kUnset = -1
+};
+
+// Possible ways an update attempt can end.
+//
+// This is used in the UpdateEngine.Attempt.Result histogram.
+enum class AttemptResult {
+  kUpdateSucceeded,             // The update succeeded.
+  kInternalError,               // An internal error occurred.
+  kPayloadDownloadError,        // Failure while downloading payload.
+  kMetadataMalformed,           // Metadata was malformed.
+  kOperationMalformed,          // An operation was malformed.
+  kOperationExecutionError,     // An operation failed to execute.
+  kMetadataVerificationFailed,  // Metadata verification failed.
+  kPayloadVerificationFailed,   // Payload verification failed.
+  kVerificationFailed,          // Root or Kernel partition verification failed.
+  kPostInstallFailed,           // The postinstall step failed.
+  kAbnormalTermination,         // The attempt ended abnormally.
+
+  kNumConstants,
+
+  kUnset = -1
+};
+
+// Possible ways the device is connected to the Internet.
+//
+// This is used in the UpdateEngine.Attempt.ConnectionType histogram.
+enum class ConnectionType {
+  kUnknown,           // Unknown.
+  kEthernet,          // Ethernet.
+  kWifi,              // Wireless.
+  kWimax,             // WiMax.
+  kBluetooth,         // Bluetooth.
+  kCellular,          // Cellular.
+  kTetheredEthernet,  // Tethered (Ethernet).
+  kTetheredWifi,      // Tethered (Wifi).
+
+  kNumConstants,
+  kUnset = -1
+};
+
+// Possible ways a rollback can end.
+//
+// This is used in the UpdateEngine.Rollback histogram.
+enum class RollbackResult {
+  kFailed,
+  kSuccess,
+
+  kNumConstants
+};
+
+// Helper function to report metrics related to rollback. The
+// following metrics are reported:
+//
+//  |kMetricRollbackResult|
+void ReportRollbackMetrics(SystemState *system_state,
+                           RollbackResult result);
+
+// Helper function to report metrics reported once a day. The
+// following metrics are reported:
+//
+//  |kMetricDailyOSAgeDays|
+void ReportDailyMetrics(SystemState *system_state,
+                        base::TimeDelta os_age);
+
+// Helper function to report metrics after completing an update check
+// with the ChromeOS update server ("Omaha"). The following metrics
+// are reported:
+//
+//  |kMetricCheckResult|
+//  |kMetricCheckReaction|
+//  |kMetricCheckDownloadErrorCode|
+//  |kMetricCheckTimeSinceLastCheckMinutes|
+//  |kMetricCheckTimeSinceLastCheckUptimeMinutes|
+//
+// The |kMetricCheckResult| metric will only be reported if |result|
+// is not |kUnset|.
+//
+// The |kMetricCheckReaction| metric will only be reported if
+// |reaction| is not |kUnset|.
+//
+// The |kMetricCheckDownloadErrorCode| will only be reported if
+// |download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricCheckTimeSinceLastCheckMinutes| and
+// |kMetricCheckTimeSinceLastCheckUptimeMinutes| metrics are
+// automatically reported and calculated by maintaining persistent
+// and process-local state variables.
+void ReportUpdateCheckMetrics(SystemState *system_state,
+                              CheckResult result,
+                              CheckReaction reaction,
+                              DownloadErrorCode download_error_code);
+
+
+// Helper function to report metrics after the completion of each
+// update attempt. The following metrics are reported:
+//
+//  |kMetricAttemptNumber|
+//  |kMetricAttemptPayloadType|
+//  |kMetricAttemptPayloadSizeMiB|
+//  |kMetricAttemptDurationSeconds|
+//  |kMetricAttemptDurationUptimeSeconds|
+//  |kMetricAttemptTimeSinceLastAttemptMinutes|
+//  |kMetricAttemptTimeSinceLastAttemptUptimeMinutes|
+//  |kMetricAttemptPayloadBytesDownloadedMiB|
+//  |kMetricAttemptPayloadDownloadSpeedKBps|
+//  |kMetricAttemptDownloadSource|
+//  |kMetricAttemptResult|
+//  |kMetricAttemptInternalErrorCode|
+//  |kMetricAttemptDownloadErrorCode|
+//
+// The |kMetricAttemptInternalErrorCode| metric will only be reported
+// if |internal_error_code| is not |kErrorSuccess|.
+//
+// The |kMetricAttemptDownloadErrorCode| metric will only be
+// reported if |payload_download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricAttemptTimeSinceLastAttemptMinutes| and
+// |kMetricAttemptTimeSinceLastAttemptUptimeMinutes| metrics are
+// automatically calculated and reported by maintaining persistent and
+// process-local state variables.
+void ReportUpdateAttemptMetrics(
+    SystemState *system_state,
+    int attempt_number,
+    PayloadType payload_type,
+    base::TimeDelta duration,
+    base::TimeDelta duration_uptime,
+    int64_t payload_size,
+    int64_t payload_bytes_downloaded,
+    int64_t payload_download_speed_bps,
+    DownloadSource download_source,
+    AttemptResult attempt_result,
+    ErrorCode internal_error_code,
+    DownloadErrorCode payload_download_error_code,
+    ConnectionType connection_type);
+
+// Reports the |kAbnormalTermination| for the |kMetricAttemptResult|
+// metric. No other metrics in the UpdateEngine.Attempt.* namespace
+// will be reported.
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(SystemState *system_state);
+
+// Helper function to report the after the completion of a successful
+// update attempt. The following metrics are reported:
+//
+//  |kMetricSuccessfulUpdateAttemptCount|
+//  |kMetricSuccessfulUpdateUpdatesAbandonedCount|
+//  |kMetricSuccessfulUpdatePayloadType|
+//  |kMetricSuccessfulUpdatePayloadSizeMiB|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpsServer|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpServer|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiBHttpPeer|
+//  |kMetricSuccessfulUpdateBytesDownloadedMiB|
+//  |kMetricSuccessfulUpdateDownloadSourcesUsed|
+//  |kMetricSuccessfulUpdateDownloadOverheadPercentage|
+//  |kMetricSuccessfulUpdateTotalDurationMinutes|
+//  |kMetricSuccessfulUpdateRebootCount|
+//  |kMetricSuccessfulUpdateUrlSwitchCount|
+//
+// The values for the |kMetricSuccessfulUpdateDownloadSourcesUsed| are
+// |kMetricSuccessfulUpdateBytesDownloadedMiB| metrics automatically
+// calculated from examining the |num_bytes_downloaded| array.
+void ReportSuccessfulUpdateMetrics(
+    SystemState *system_state,
+    int attempt_count,
+    int updates_abandoned_count,
+    PayloadType payload_type,
+    int64_t payload_size,
+    int64_t num_bytes_downloaded[kNumDownloadSources],
+    int download_overhead_percentage,
+    base::TimeDelta total_duration,
+    int reboot_count,
+    int url_switch_count);
+
+}  // namespace metrics
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_METRICS_H_
diff --git a/mock_action.h b/mock_action.h
new file mode 100644
index 0000000..f7bfedd
--- /dev/null
+++ b/mock_action.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2010 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_MOCK_ACTION_H_
+#define UPDATE_ENGINE_MOCK_ACTION_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/action.h"
+
+namespace chromeos_update_engine {
+
+class MockAction;
+
+template<>
+class ActionTraits<MockAction> {
+ public:
+  typedef NoneType OutputObjectType;
+  typedef NoneType InputObjectType;
+};
+
+class MockAction : public Action<MockAction> {
+ public:
+  MOCK_METHOD0(PerformAction, void());
+  MOCK_CONST_METHOD0(Type, std::string());
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_ACTION_H_
diff --git a/mock_action_processor.h b/mock_action_processor.h
new file mode 100644
index 0000000..24297ea
--- /dev/null
+++ b/mock_action_processor.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 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_MOCK_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_MOCK_ACTION_PROCESSOR_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/action.h"
+
+namespace chromeos_update_engine {
+
+class MockActionProcessor : public ActionProcessor {
+ public:
+  MOCK_METHOD0(StartProcessing, void());
+  MOCK_METHOD1(EnqueueAction, void(AbstractAction* action));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_ACTION_PROCESSOR_H_
diff --git a/mock_certificate_checker.h b/mock_certificate_checker.h
new file mode 100644
index 0000000..8c26b42
--- /dev/null
+++ b/mock_certificate_checker.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 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_MOCK_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_
+
+#include <gmock/gmock.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/certificate_checker.h"
+
+namespace chromeos_update_engine {
+
+class MockOpenSSLWrapper : public OpenSSLWrapper {
+ public:
+  MOCK_CONST_METHOD4(GetCertificateDigest,
+                     bool(X509_STORE_CTX* x509_ctx,
+                          int* out_depth,
+                          unsigned int* out_digest_length,
+                          uint8_t* out_digest));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_
diff --git a/mock_connection_manager.h b/mock_connection_manager.h
new file mode 100644
index 0000000..9530865
--- /dev/null
+++ b/mock_connection_manager.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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_MOCK_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/connection_manager.h"
+
+namespace chromeos_update_engine {
+
+// This class mocks the generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class MockConnectionManager : public ConnectionManager {
+ public:
+  explicit MockConnectionManager(SystemState* system_state)
+      : ConnectionManager(system_state) {}
+
+  MOCK_CONST_METHOD3(GetConnectionProperties,
+                     bool(DBusWrapperInterface* dbus_iface,
+                          NetworkConnectionType* out_type,
+                          NetworkTethering* out_tethering));
+
+  MOCK_CONST_METHOD2(IsUpdateAllowedOver, bool(NetworkConnectionType type,
+                                               NetworkTethering tethering));
+
+  MOCK_CONST_METHOD1(StringForConnectionType,
+      const char*(NetworkConnectionType type));
+
+  MOCK_CONST_METHOD1(StringForTethering,
+      const char*(NetworkTethering tethering));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
diff --git a/mock_dbus_wrapper.h b/mock_dbus_wrapper.h
new file mode 100644
index 0000000..5d630a6
--- /dev/null
+++ b/mock_dbus_wrapper.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2010 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_MOCK_DBUS_WRAPPER_H_
+#define UPDATE_ENGINE_MOCK_DBUS_WRAPPER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockDBusWrapper : public DBusWrapperInterface {
+ public:
+  MOCK_METHOD4(ProxyNewForName, DBusGProxy*(DBusGConnection *connection,
+                                            const char *name,
+                                            const char *path,
+                                            const char *interface));
+
+  MOCK_METHOD1(ProxyUnref, void(DBusGProxy* proxy));
+
+  MOCK_METHOD2(BusGet, DBusGConnection*(DBusBusType type, GError **error));
+
+  MOCK_METHOD4(ProxyCall_0_1, gboolean(DBusGProxy *proxy,
+                                       const char *method,
+                                       GError **error,
+                                       GHashTable** out1));
+  MOCK_METHOD4(ProxyCall_0_1, gboolean(DBusGProxy *proxy,
+                                       const char *method,
+                                       GError **error,
+                                       gint* out1));
+  MOCK_METHOD4(ProxyCall_1_0, gboolean(DBusGProxy *proxy,
+                                       const char *method,
+                                       GError **error,
+                                       gint in1));
+  MOCK_METHOD6(ProxyCall_3_0, gboolean(DBusGProxy* proxy,
+                                       const char* method,
+                                       GError** error,
+                                       const char* in1,
+                                       const char* in2,
+                                       const char* in3));
+
+  MOCK_METHOD3(ProxyAddSignal_1, void(DBusGProxy* proxy,
+                                      const char* signal_name,
+                                      GType type1));
+
+  MOCK_METHOD4(ProxyAddSignal_2, void(DBusGProxy* proxy,
+                                      const char* signal_name,
+                                      GType type1,
+                                      GType type2));
+
+  MOCK_METHOD5(ProxyConnectSignal, void(DBusGProxy* proxy,
+                                        const char* signal_name,
+                                        GCallback handler,
+                                        void* data,
+                                        GClosureNotify free_data_func));
+
+  MOCK_METHOD4(ProxyDisconnectSignal, void(DBusGProxy* proxy,
+                                           const char* signal_name,
+                                           GCallback handler,
+                                           void* data));
+
+  MOCK_METHOD1(ConnectionGetConnection, DBusConnection*(DBusGConnection* gbus));
+
+  MOCK_METHOD3(DBusBusAddMatch, void(DBusConnection* connection,
+                                     const char* rule,
+                                     DBusError* error));
+
+  MOCK_METHOD4(DBusConnectionAddFilter, dbus_bool_t(
+      DBusConnection* connection,
+      DBusHandleMessageFunction function,
+      void* user_data,
+      DBusFreeFunction free_data_function));
+
+  MOCK_METHOD3(DBusConnectionRemoveFilter, void(
+      DBusConnection* connection,
+      DBusHandleMessageFunction function,
+      void* user_data));
+
+  MOCK_METHOD3(DBusMessageIsSignal, dbus_bool_t(DBusMessage* message,
+                                                const char* interface,
+                                                const char* signal_name));
+
+  MOCK_METHOD5(DBusMessageGetArgs_3, dbus_bool_t(DBusMessage* message,
+                                                 DBusError* error,
+                                                 char** out1,
+                                                 char** out2,
+                                                 char** out3));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_DBUS_WRAPPER_H_
diff --git a/mock_file_writer.h b/mock_file_writer.h
new file mode 100644
index 0000000..65a5531
--- /dev/null
+++ b/mock_file_writer.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2010 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_MOCK_FILE_WRITER_H_
+#define UPDATE_ENGINE_MOCK_FILE_WRITER_H_
+
+#include <gmock/gmock.h>
+#include "update_engine/file_writer.h"
+
+namespace chromeos_update_engine {
+
+class MockFileWriter : public FileWriter {
+ public:
+  MOCK_METHOD3(Open, int(const char* path, int flags, mode_t mode));
+  MOCK_METHOD2(Write, ssize_t(const void* bytes, size_t count));
+  MOCK_METHOD0(Close, int());
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_FILE_WRITER_H_
diff --git a/mock_hardware.h b/mock_hardware.h
new file mode 100644
index 0000000..bf26821
--- /dev/null
+++ b/mock_hardware.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2013 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_MOCK_HARDWARE_H_
+#define UPDATE_ENGINE_MOCK_HARDWARE_H_
+
+#include <string>
+#include <vector>
+
+#include "update_engine/fake_hardware.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of HardwareInterface.
+class MockHardware : public HardwareInterface {
+ public:
+  MockHardware() {
+    // Delegate all calls to the fake instance
+    ON_CALL(*this, BootKernelDevice())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::BootKernelDevice));
+    ON_CALL(*this, BootDevice())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::BootDevice));
+    ON_CALL(*this, IsBootDeviceRemovable())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsBootDeviceRemovable));
+    ON_CALL(*this, GetKernelDevices())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetKernelDevices));
+    ON_CALL(*this, IsKernelBootable(testing::_, testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsKernelBootable));
+    ON_CALL(*this, MarkKernelUnbootable(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::MarkKernelUnbootable));
+    ON_CALL(*this, IsOfficialBuild())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsOfficialBuild));
+    ON_CALL(*this, IsNormalBootMode())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsNormalBootMode));
+    ON_CALL(*this, IsOOBEComplete(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::IsOOBEComplete));
+    ON_CALL(*this, GetHardwareClass())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetHardwareClass));
+    ON_CALL(*this, GetFirmwareVersion())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetFirmwareVersion));
+    ON_CALL(*this, GetECVersion())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetECVersion));
+    ON_CALL(*this, GetPowerwashCount())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeHardware::GetPowerwashCount));
+  }
+
+  ~MockHardware() override {}
+
+  // Hardware overrides.
+  MOCK_CONST_METHOD0(BootKernelDevice, std::string());
+  MOCK_CONST_METHOD0(BootDevice, std::string());
+  MOCK_CONST_METHOD0(IsBootDeviceRemovable, bool());
+  MOCK_CONST_METHOD0(GetKernelDevices, std::vector<std::string>());
+  MOCK_CONST_METHOD2(IsKernelBootable,
+               bool(const std::string& kernel_device, bool* bootable));
+  MOCK_METHOD1(MarkKernelUnbootable,
+               bool(const std::string& kernel_device));
+  MOCK_CONST_METHOD0(IsOfficialBuild, bool());
+  MOCK_CONST_METHOD0(IsNormalBootMode, bool());
+  MOCK_CONST_METHOD1(IsOOBEComplete, bool(base::Time* out_time_of_oobe));
+  MOCK_CONST_METHOD0(GetHardwareClass, std::string());
+  MOCK_CONST_METHOD0(GetFirmwareVersion, std::string());
+  MOCK_CONST_METHOD0(GetECVersion, std::string());
+  MOCK_CONST_METHOD0(GetPowerwashCount, int());
+
+  // Returns a reference to the underlying FakeHardware.
+  FakeHardware& fake() {
+    return fake_;
+  }
+
+ private:
+  // The underlying FakeHardware.
+  FakeHardware fake_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockHardware);
+};
+
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_HARDWARE_H_
diff --git a/mock_http_fetcher.cc b/mock_http_fetcher.cc
new file mode 100644
index 0000000..a8dd1ae
--- /dev/null
+++ b/mock_http_fetcher.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2009 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/mock_http_fetcher.h"
+
+#include <algorithm>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+
+using chromeos::MessageLoop;
+using std::min;
+
+namespace chromeos_update_engine {
+
+MockHttpFetcher::~MockHttpFetcher() {
+  CHECK(timeout_id_ == MessageLoop::kTaskIdNull) <<
+      "Call TerminateTransfer() before dtor.";
+}
+
+void MockHttpFetcher::BeginTransfer(const std::string& url) {
+  EXPECT_FALSE(never_use_);
+  if (fail_transfer_ || data_.empty()) {
+    // No data to send, just notify of completion..
+    SignalTransferComplete();
+    return;
+  }
+  if (sent_size_ < data_.size())
+    SendData(true);
+}
+
+// Returns false on one condition: If timeout_id_ was already set
+// and it needs to be deleted by the caller. If timeout_id_ is null
+// when this function is called, this function will always return true.
+bool MockHttpFetcher::SendData(bool skip_delivery) {
+  if (fail_transfer_) {
+    SignalTransferComplete();
+    return timeout_id_ != MessageLoop::kTaskIdNull;
+  }
+
+  CHECK_LT(sent_size_, data_.size());
+  if (!skip_delivery) {
+    const size_t chunk_size = min(kMockHttpFetcherChunkSize,
+                                  data_.size() - sent_size_);
+    CHECK(delegate_);
+    delegate_->ReceivedBytes(this, &data_[sent_size_], chunk_size);
+    // We may get terminated in the callback.
+    if (sent_size_ == data_.size()) {
+      LOG(INFO) << "Terminated in the ReceivedBytes callback.";
+      return timeout_id_ != MessageLoop::kTaskIdNull;
+    }
+    sent_size_ += chunk_size;
+    CHECK_LE(sent_size_, data_.size());
+    if (sent_size_ == data_.size()) {
+      // We've sent all the data. Notify of success.
+      SignalTransferComplete();
+    }
+  }
+
+  if (paused_) {
+    // If we're paused, we should return true if timeout_id_ is set,
+    // since we need the caller to delete it.
+    return timeout_id_ != MessageLoop::kTaskIdNull;
+  }
+
+  if (timeout_id_ != MessageLoop::kTaskIdNull) {
+    // we still need a timeout if there's more data to send
+    return sent_size_ < data_.size();
+  } else if (sent_size_ < data_.size()) {
+    // we don't have a timeout source and we need one
+    timeout_id_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+        base::TimeDelta::FromMilliseconds(10));
+  }
+  return true;
+}
+
+void MockHttpFetcher::TimeoutCallback() {
+  CHECK(!paused_);
+  if (SendData(false)) {
+    // We need to re-schedule the timeout.
+    timeout_id_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+        base::TimeDelta::FromMilliseconds(10));
+  } else {
+    timeout_id_ = MessageLoop::kTaskIdNull;
+  }
+}
+
+// If the transfer is in progress, aborts the transfer early.
+// The transfer cannot be resumed.
+void MockHttpFetcher::TerminateTransfer() {
+  LOG(INFO) << "Terminating transfer.";
+  sent_size_ = data_.size();
+  // Kill any timeout, it is ok to call with kTaskIdNull.
+  MessageLoop::current()->CancelTask(timeout_id_);
+  timeout_id_ = MessageLoop::kTaskIdNull;
+  delegate_->TransferTerminated(this);
+}
+
+void MockHttpFetcher::Pause() {
+  CHECK(!paused_);
+  paused_ = true;
+  MessageLoop::current()->CancelTask(timeout_id_);
+  timeout_id_ = MessageLoop::kTaskIdNull;
+}
+
+void MockHttpFetcher::Unpause() {
+  CHECK(paused_) << "You must pause before unpause.";
+  paused_ = false;
+  if (sent_size_ < data_.size()) {
+    SendData(false);
+  }
+}
+
+void MockHttpFetcher::FailTransfer(int http_response_code) {
+  fail_transfer_ = true;
+  http_response_code_ = http_response_code;
+}
+
+void MockHttpFetcher::SignalTransferComplete() {
+  // If the transfer has been failed, the HTTP response code should be set
+  // already.
+  if (!fail_transfer_) {
+    http_response_code_ = 200;
+  }
+  delegate_->TransferComplete(this, !fail_transfer_);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/mock_http_fetcher.h b/mock_http_fetcher.h
new file mode 100644
index 0000000..89f5d21
--- /dev/null
+++ b/mock_http_fetcher.h
@@ -0,0 +1,142 @@
+// Copyright (c) 2009 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_MOCK_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_MOCK_HTTP_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <glib.h>
+
+#include "update_engine/fake_system_state.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/mock_connection_manager.h"
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+// All data must be passed into the ctor. When started, MockHttpFetcher will
+// deliver the data in chunks of size kMockHttpFetcherChunkSize. To simulate
+// a network failure, you can call FailTransfer().
+
+namespace chromeos_update_engine {
+
+// MockHttpFetcher will send a chunk of data down in each call to BeginTransfer
+// and Unpause. For the other chunks of data, a callback is put on the run
+// loop and when that's called, another chunk is sent down.
+const size_t kMockHttpFetcherChunkSize(65536);
+
+class MockHttpFetcher : public HttpFetcher {
+ public:
+  // The data passed in here is copied and then passed to the delegate after
+  // the transfer begins.
+  MockHttpFetcher(const uint8_t* data,
+                  size_t size,
+                  ProxyResolver* proxy_resolver)
+      : HttpFetcher(proxy_resolver, &fake_system_state_),
+        sent_size_(0),
+        timeout_id_(chromeos::MessageLoop::kTaskIdNull),
+        paused_(false),
+        fail_transfer_(false),
+        never_use_(false),
+        mock_connection_manager_(&fake_system_state_) {
+    fake_system_state_.set_connection_manager(&mock_connection_manager_);
+    data_.insert(data_.end(), data, data + size);
+  }
+
+  // Constructor overload for string data.
+  MockHttpFetcher(const char* data, size_t size, ProxyResolver* proxy_resolver)
+      : MockHttpFetcher(reinterpret_cast<const uint8_t*>(data), size,
+                        proxy_resolver) {}
+
+  // Cleans up all internal state. Does not notify delegate
+  ~MockHttpFetcher() override;
+
+  // Ignores this.
+  void SetOffset(off_t offset) override {
+    sent_size_ = offset;
+    if (delegate_)
+      delegate_->SeekToOffset(offset);
+  }
+
+  // Do nothing.
+  void SetLength(size_t length) override {}
+  void UnsetLength() override {}
+  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {}
+  void set_connect_timeout(int connect_timeout_seconds) override {}
+  void set_max_retry_count(int max_retry_count) override {}
+
+  // Dummy: no bytes were downloaded.
+  size_t GetBytesDownloaded() override {
+    return sent_size_;
+  }
+
+  // Begins the transfer if it hasn't already begun.
+  void BeginTransfer(const std::string& url) override;
+
+  // If the transfer is in progress, aborts the transfer early.
+  // The transfer cannot be resumed.
+  void TerminateTransfer() override;
+
+  // Suspend the mock transfer.
+  void Pause() override;
+
+  // Resume the mock transfer.
+  void Unpause() override;
+
+  // Fail the transfer. This simulates a network failure.
+  void FailTransfer(int http_response_code);
+
+  // If set to true, this will EXPECT fail on BeginTransfer
+  void set_never_use(bool never_use) { never_use_ = never_use; }
+
+  const chromeos::Blob& post_data() const {
+    return post_data_;
+  }
+
+ private:
+  // Sends data to the delegate and sets up a timeout callback if needed.
+  // There must be a delegate and there must be data to send. If there is
+  // already a timeout callback, and it should be deleted by the caller,
+  // this will return false; otherwise true is returned.
+  // If skip_delivery is true, no bytes will be delivered, but the callbacks
+  // still be set if needed.
+  bool SendData(bool skip_delivery);
+
+  // Callback for when our message loop timeout expires.
+  void TimeoutCallback();
+
+  // Sets the HTTP response code and signals to the delegate that the transfer
+  // is complete.
+  void SignalTransferComplete();
+
+  // A full copy of the data we'll return to the delegate
+  chromeos::Blob data_;
+
+  // The number of bytes we've sent so far
+  size_t sent_size_;
+
+  // The TaskId of the timeout callback. After each chunk of data sent, we
+  // time out for 0s just to make sure that run loop services other clients.
+  chromeos::MessageLoop::TaskId timeout_id_;
+
+  // True iff the fetcher is paused.
+  bool paused_;
+
+  // Set to true if the transfer should fail.
+  bool fail_transfer_;
+
+  // Set to true if BeginTransfer should EXPECT fail.
+  bool never_use_;
+
+  FakeSystemState fake_system_state_;
+  MockConnectionManager mock_connection_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_HTTP_FETCHER_H_
diff --git a/mock_omaha_request_params.h b/mock_omaha_request_params.h
new file mode 100644
index 0000000..f04d361
--- /dev/null
+++ b/mock_omaha_request_params.h
@@ -0,0 +1,75 @@
+// Copyright 2014 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_MOCK_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/omaha_request_params.h"
+
+namespace chromeos_update_engine {
+
+class MockOmahaRequestParams : public OmahaRequestParams {
+ public:
+  explicit MockOmahaRequestParams(SystemState* system_state)
+      : OmahaRequestParams(system_state) {
+    // Delegate all calls to the parent instance by default. This helps the
+    // migration from tests using the real RequestParams when they should have
+    // use a fake or mock.
+    ON_CALL(*this, to_more_stable_channel())
+        .WillByDefault(testing::Invoke(
+            this, &MockOmahaRequestParams::fake_to_more_stable_channel));
+    ON_CALL(*this, GetAppId())
+        .WillByDefault(testing::Invoke(
+            this, &MockOmahaRequestParams::FakeGetAppId));
+    ON_CALL(*this, SetTargetChannel(testing::_, testing::_))
+        .WillByDefault(testing::Invoke(
+            this, &MockOmahaRequestParams::FakeSetTargetChannel));
+    ON_CALL(*this, UpdateDownloadChannel())
+        .WillByDefault(testing::Invoke(
+            this, &MockOmahaRequestParams::FakeUpdateDownloadChannel));
+    ON_CALL(*this, is_powerwash_allowed())
+        .WillByDefault(testing::Invoke(
+            this, &MockOmahaRequestParams::fake_is_powerwash_allowed));
+  }
+
+  MOCK_CONST_METHOD0(to_more_stable_channel, bool(void));
+  MOCK_CONST_METHOD0(GetAppId, std::string(void));
+  MOCK_METHOD2(SetTargetChannel, bool(const std::string& channel,
+                                      bool is_powerwash_allowed));
+  MOCK_METHOD0(UpdateDownloadChannel, void(void));
+  MOCK_CONST_METHOD0(is_powerwash_allowed, bool(void));
+  MOCK_CONST_METHOD0(IsUpdateUrlOfficial, bool(void));
+
+ private:
+  // Wrappers to call the parent class and behave like the real object by
+  // default. See "Delegating Calls to a Parent Class" in gmock's documentation.
+  bool fake_to_more_stable_channel() const {
+    return OmahaRequestParams::to_more_stable_channel();
+  }
+
+  std::string FakeGetAppId() const {
+    return OmahaRequestParams::GetAppId();
+  }
+
+  bool FakeSetTargetChannel(const std::string& channel,
+                            bool is_powerwash_allowed) {
+    return OmahaRequestParams::SetTargetChannel(channel, is_powerwash_allowed);
+  }
+
+  void FakeUpdateDownloadChannel() {
+    return OmahaRequestParams::UpdateDownloadChannel();
+  }
+
+  bool fake_is_powerwash_allowed() const {
+    return OmahaRequestParams::is_powerwash_allowed();
+  }
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
diff --git a/mock_p2p_manager.h b/mock_p2p_manager.h
new file mode 100644
index 0000000..6b5a75e
--- /dev/null
+++ b/mock_p2p_manager.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2013 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_MOCK_P2P_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/fake_p2p_manager.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of P2PManager.
+class MockP2PManager : public P2PManager {
+ public:
+  MockP2PManager() {
+    // Delegate all calls to the fake instance
+    ON_CALL(*this, SetDevicePolicy(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::SetDevicePolicy));
+    ON_CALL(*this, IsP2PEnabled())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::IsP2PEnabled));
+    ON_CALL(*this, EnsureP2PRunning())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::EnsureP2PRunning));
+    ON_CALL(*this, EnsureP2PNotRunning())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::EnsureP2PNotRunning));
+    ON_CALL(*this, PerformHousekeeping())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::PerformHousekeeping));
+    ON_CALL(*this, LookupUrlForFile(testing::_, testing::_, testing::_,
+                                    testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::LookupUrlForFile));
+    ON_CALL(*this, FileShare(testing::_, testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::FileShare));
+    ON_CALL(*this, FileGetPath(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::FileGetPath));
+    ON_CALL(*this, FileGetSize(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::FileGetSize));
+    ON_CALL(*this, FileGetExpectedSize(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::FileGetExpectedSize));
+    ON_CALL(*this, FileGetVisible(testing::_, testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::FileGetVisible));
+    ON_CALL(*this, FileMakeVisible(testing::_))
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::FileMakeVisible));
+    ON_CALL(*this, CountSharedFiles())
+      .WillByDefault(testing::Invoke(&fake_,
+            &FakeP2PManager::CountSharedFiles));
+  }
+
+  ~MockP2PManager() override {}
+
+  // P2PManager overrides.
+  MOCK_METHOD1(SetDevicePolicy, void(const policy::DevicePolicy*));
+  MOCK_METHOD0(IsP2PEnabled, bool());
+  MOCK_METHOD0(EnsureP2PRunning, bool());
+  MOCK_METHOD0(EnsureP2PNotRunning, bool());
+  MOCK_METHOD0(PerformHousekeeping, bool());
+  MOCK_METHOD4(LookupUrlForFile, void(const std::string&,
+                                      size_t,
+                                      base::TimeDelta,
+                                      LookupCallback));
+  MOCK_METHOD2(FileShare, bool(const std::string&, size_t));
+  MOCK_METHOD1(FileGetPath, base::FilePath(const std::string&));
+  MOCK_METHOD1(FileGetSize, ssize_t(const std::string&));
+  MOCK_METHOD1(FileGetExpectedSize, ssize_t(const std::string&));
+  MOCK_METHOD2(FileGetVisible, bool(const std::string&, bool*));
+  MOCK_METHOD1(FileMakeVisible, bool(const std::string&));
+  MOCK_METHOD0(CountSharedFiles, int());
+
+  // Returns a reference to the underlying FakeP2PManager.
+  FakeP2PManager& fake() {
+    return fake_;
+  }
+
+ private:
+  // The underlying FakeP2PManager.
+  FakeP2PManager fake_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockP2PManager);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
diff --git a/mock_payload_state.h b/mock_payload_state.h
new file mode 100644
index 0000000..2ac7e97
--- /dev/null
+++ b/mock_payload_state.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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_MOCK_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/payload_state_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPayloadState: public PayloadStateInterface {
+ public:
+  bool Initialize(SystemState* system_state) {
+    return true;
+  }
+
+  // Significant methods.
+  MOCK_METHOD1(SetResponse, void(const OmahaResponse& response));
+  MOCK_METHOD0(DownloadComplete, void());
+  MOCK_METHOD1(DownloadProgress, void(size_t count));
+  MOCK_METHOD0(UpdateResumed, void());
+  MOCK_METHOD0(UpdateRestarted, void());
+  MOCK_METHOD0(UpdateSucceeded, void());
+  MOCK_METHOD1(UpdateFailed, void(ErrorCode error));
+  MOCK_METHOD0(ResetUpdateStatus, void());
+  MOCK_METHOD0(ShouldBackoffDownload, bool());
+  MOCK_METHOD0(UpdateEngineStarted, void());
+  MOCK_METHOD0(Rollback, void());
+  MOCK_METHOD1(ExpectRebootInNewVersion,
+               void(const std::string& target_version_uid));
+  MOCK_METHOD0(P2PNewAttempt, void());
+  MOCK_METHOD0(P2PAttemptAllowed, bool());
+  MOCK_METHOD1(SetUsingP2PForDownloading, void(bool value));
+  MOCK_METHOD1(SetUsingP2PForSharing, void(bool value));
+  MOCK_METHOD1(SetScatteringWaitPeriod, void(base::TimeDelta));
+  MOCK_METHOD1(SetP2PUrl, void(const std::string&));
+
+  // Getters.
+  MOCK_METHOD0(GetResponseSignature, std::string());
+  MOCK_METHOD0(GetPayloadAttemptNumber, int());
+  MOCK_METHOD0(GetFullPayloadAttemptNumber, int());
+  MOCK_METHOD0(GetCurrentUrl, std::string());
+  MOCK_METHOD0(GetUrlFailureCount, uint32_t());
+  MOCK_METHOD0(GetUrlSwitchCount, uint32_t());
+  MOCK_METHOD0(GetNumResponsesSeen, int());
+  MOCK_METHOD0(GetBackoffExpiryTime, base::Time());
+  MOCK_METHOD0(GetUpdateDuration, base::TimeDelta());
+  MOCK_METHOD0(GetUpdateDurationUptime, base::TimeDelta());
+  MOCK_METHOD1(GetCurrentBytesDownloaded, uint64_t(DownloadSource source));
+  MOCK_METHOD1(GetTotalBytesDownloaded, uint64_t(DownloadSource source));
+  MOCK_METHOD0(GetNumReboots, uint32_t());
+  MOCK_METHOD0(GetRollbackVersion, std::string());
+  MOCK_METHOD0(GetP2PNumAttempts, int());
+  MOCK_METHOD0(GetP2PFirstAttemptTimestamp, base::Time());
+  MOCK_CONST_METHOD0(GetUsingP2PForDownloading, bool());
+  MOCK_CONST_METHOD0(GetUsingP2PForSharing, bool());
+  MOCK_METHOD0(GetScatteringWaitPeriod, base::TimeDelta());
+  MOCK_CONST_METHOD0(GetP2PUrl, std::string());
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
diff --git a/mock_prefs.h b/mock_prefs.h
new file mode 100644
index 0000000..176176b
--- /dev/null
+++ b/mock_prefs.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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_MOCK_PREFS_H_
+#define UPDATE_ENGINE_MOCK_PREFS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPrefs : public PrefsInterface {
+ public:
+  MOCK_METHOD2(GetString, bool(const std::string& key, std::string* value));
+  MOCK_METHOD2(SetString, bool(const std::string& key,
+                               const std::string& value));
+  MOCK_METHOD2(GetInt64, bool(const std::string& key, int64_t* value));
+  MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value));
+
+  MOCK_METHOD2(GetBoolean, bool(const std::string& key, bool* value));
+  MOCK_METHOD2(SetBoolean, bool(const std::string& key, const bool value));
+
+  MOCK_METHOD1(Exists, bool(const std::string& key));
+  MOCK_METHOD1(Delete, bool(const std::string& key));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_PREFS_H_
diff --git a/mock_update_attempter.h b/mock_update_attempter.h
new file mode 100644
index 0000000..0c20311
--- /dev/null
+++ b/mock_update_attempter.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2010 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_MOCK_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
+
+#include <string>
+
+#include "update_engine/update_attempter.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockUpdateAttempter : public UpdateAttempter {
+ public:
+  using UpdateAttempter::UpdateAttempter;
+
+  MOCK_METHOD6(Update, void(const std::string& app_version,
+                            const std::string& omaha_url,
+                            const std::string& target_channel,
+                            const std::string& target_version_prefix,
+                            bool obey_proxies,
+                            bool interactive));
+
+  MOCK_METHOD5(GetStatus, bool(int64_t* last_checked_time,
+                               double* progress,
+                               std::string* current_operation,
+                               std::string* new_version,
+                               int64_t* new_size));
+
+  MOCK_METHOD1(GetBootTimeAtUpdate, bool(base::Time* out_boot_time));
+
+  MOCK_METHOD0(ResetStatus, bool(void));
+
+  MOCK_METHOD3(CheckForUpdate, void(const std::string& app_version,
+                                    const std::string& omaha_url,
+                                    bool is_interactive));
+
+  MOCK_METHOD0(RefreshDevicePolicy, void(void));
+
+  MOCK_CONST_METHOD0(consecutive_failed_update_checks, unsigned int(void));
+
+  MOCK_CONST_METHOD0(server_dictated_poll_interval, unsigned int(void));
+
+  MOCK_METHOD0(IsAnyUpdateSourceAllowed, bool(void));
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
diff --git a/mtd_file_descriptor.cc b/mtd_file_descriptor.cc
new file mode 100644
index 0000000..7ab2afa
--- /dev/null
+++ b/mtd_file_descriptor.cc
@@ -0,0 +1,254 @@
+// Copyright 2014 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/mtd_file_descriptor.h"
+
+#include <fcntl.h>
+#include <mtd/ubi-user.h>
+#include <string>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <update_engine/subprocess.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+static const char kSysfsClassUbi[] = "/sys/class/ubi/";
+static const char kUsableEbSize[] = "/usable_eb_size";
+static const char kReservedEbs[] = "/reserved_ebs";
+
+using chromeos_update_engine::Subprocess;
+using chromeos_update_engine::UbiVolumeInfo;
+using chromeos_update_engine::utils::ReadFile;
+
+// Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return
+// a null unique pointer.
+std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) {
+  base::FilePath device_node(path);
+  base::FilePath ubi_name(device_node.BaseName());
+
+  std::string sysfs_node(kSysfsClassUbi);
+  sysfs_node.append(ubi_name.MaybeAsASCII());
+
+  std::unique_ptr<UbiVolumeInfo> ret;
+
+  // Obtain volume info from sysfs.
+  std::string s_reserved_ebs;
+  if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) {
+    LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs;
+    return ret;
+  }
+  std::string s_eb_size;
+  if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) {
+    LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize;
+    return ret;
+  }
+
+  base::TrimWhitespaceASCII(s_reserved_ebs,
+                            base::TRIM_TRAILING,
+                            &s_reserved_ebs);
+  base::TrimWhitespaceASCII(s_eb_size, base::TRIM_TRAILING, &s_eb_size);
+
+  uint64_t reserved_ebs, eb_size;
+  if (!base::StringToUint64(s_reserved_ebs, &reserved_ebs)) {
+    LOG(ERROR) << "Cannot parse reserved_ebs: " << s_reserved_ebs;
+    return ret;
+  }
+  if (!base::StringToUint64(s_eb_size, &eb_size)) {
+    LOG(ERROR) << "Cannot parse usable_eb_size: " << s_eb_size;
+    return ret;
+  }
+
+  ret.reset(new UbiVolumeInfo);
+  ret->reserved_ebs = reserved_ebs;
+  ret->eraseblock_size = eb_size;
+  return ret;
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+MtdFileDescriptor::MtdFileDescriptor()
+    : read_ctx_(nullptr, &mtd_read_close),
+      write_ctx_(nullptr, &mtd_write_close) {}
+
+bool MtdFileDescriptor::IsMtd(const char* path) {
+  uint64_t size;
+  return mtd_node_info(path, &size, nullptr, nullptr) == 0;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  // This File Descriptor does not support read and write.
+  TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+  // But we need to open the underlying file descriptor in O_RDWR mode because
+  // during write, we need to read back to verify the write actually sticks or
+  // we have to skip the block. That job is done by mtdutils library.
+  if ((flags & O_ACCMODE) == O_WRONLY) {
+    flags &= ~O_ACCMODE;
+    flags |= O_RDWR;
+  }
+  TEST_AND_RETURN_FALSE(
+      EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+  if ((flags & O_ACCMODE) == O_RDWR) {
+    write_ctx_.reset(mtd_write_descriptor(fd_, path));
+    nr_written_ = 0;
+  } else {
+    read_ctx_.reset(mtd_read_descriptor(fd_, path));
+  }
+
+  if (!read_ctx_ && !write_ctx_) {
+    Close();
+    return false;
+  }
+
+  return true;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags) {
+  mode_t cur = umask(022);
+  umask(cur);
+  return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t MtdFileDescriptor::Read(void* buf, size_t count) {
+  CHECK(read_ctx_);
+  return mtd_read_data(read_ctx_.get(), static_cast<char*>(buf), count);
+}
+
+ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) {
+  CHECK(write_ctx_);
+  ssize_t result = mtd_write_data(write_ctx_.get(),
+                                  static_cast<const char*>(buf),
+                                  count);
+  if (result > 0) {
+    nr_written_ += result;
+  }
+  return result;
+}
+
+off64_t MtdFileDescriptor::Seek(off64_t offset, int whence) {
+  if (write_ctx_) {
+    // Ignore seek in write mode.
+    return nr_written_;
+  }
+  return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool MtdFileDescriptor::Close() {
+  read_ctx_.reset();
+  write_ctx_.reset();
+  return EintrSafeFileDescriptor::Close();
+}
+
+bool UbiFileDescriptor::IsUbi(const char* path) {
+  base::FilePath device_node(path);
+  base::FilePath ubi_name(device_node.BaseName());
+  TEST_AND_RETURN_FALSE(
+      base::StartsWithASCII(ubi_name.MaybeAsASCII(), "ubi", true));
+
+  return static_cast<bool>(GetUbiVolumeInfo(path));
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path);
+  if (!info) {
+    return false;
+  }
+
+  // This File Descriptor does not support read and write.
+  TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+  TEST_AND_RETURN_FALSE(
+      EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+  usable_eb_blocks_ = info->reserved_ebs;
+  eraseblock_size_ = info->eraseblock_size;
+  volume_size_ = usable_eb_blocks_ * eraseblock_size_;
+
+  if ((flags & O_ACCMODE) == O_WRONLY) {
+    // It's best to use volume update ioctl so that UBI layer will mark the
+    // volume as being updated, and only clear that mark if the update is
+    // successful. We will need to pad to the whole volume size at close.
+    uint64_t vsize = volume_size_;
+    if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) {
+      PLOG(ERROR) << "Cannot issue volume update ioctl";
+      EintrSafeFileDescriptor::Close();
+      return false;
+    }
+    mode_ = kWriteOnly;
+    nr_written_ = 0;
+  } else {
+    mode_ = kReadOnly;
+  }
+
+  return true;
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags) {
+  mode_t cur = umask(022);
+  umask(cur);
+  return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t UbiFileDescriptor::Read(void* buf, size_t count) {
+  CHECK(mode_ == kReadOnly);
+  return EintrSafeFileDescriptor::Read(buf, count);
+}
+
+ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) {
+  CHECK(mode_ == kWriteOnly);
+  ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count);
+  if (nr_chunk >= 0) {
+    nr_written_ += nr_chunk;
+  }
+  return nr_chunk;
+}
+
+off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) {
+  if (mode_ == kWriteOnly) {
+    // Ignore seek in write mode.
+    return nr_written_;
+  }
+  return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool UbiFileDescriptor::Close() {
+  bool pad_ok = true;
+  if (IsOpen() && mode_ == kWriteOnly) {
+    char buf[1024];
+    memset(buf, 0xFF, sizeof(buf));
+    while (nr_written_ < volume_size_) {
+      // We have written less than the whole volume. In order for us to clear
+      // the update marker, we need to fill the rest. It is recommended to fill
+      // UBI writes with 0xFF.
+      uint64_t to_write = volume_size_ - nr_written_;
+      if (to_write > sizeof(buf)) {
+        to_write = sizeof(buf);
+      }
+      ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write);
+      if (nr_chunk < 0) {
+        LOG(ERROR) << "Cannot 0xFF-pad before closing.";
+        // There is an error, but we can't really do any meaningful thing here.
+        pad_ok = false;
+        break;
+      }
+      nr_written_ += nr_chunk;
+    }
+  }
+  return EintrSafeFileDescriptor::Close() && pad_ok;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/mtd_file_descriptor.h b/mtd_file_descriptor.h
new file mode 100644
index 0000000..ad545b6
--- /dev/null
+++ b/mtd_file_descriptor.h
@@ -0,0 +1,78 @@
+// Copyright 2014 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_MTD_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_MTD_FILE_DESCRIPTOR_H_
+
+// This module defines file descriptors that deal with NAND media. We are
+// concerned with raw NAND access (as MTD device), and through UBI layer.
+
+#include <mtdutils.h>
+
+#include "update_engine/file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+// A class defining the file descriptor API for raw MTD device. This file
+// descriptor supports either random read, or sequential write but not both at
+// once.
+class MtdFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+  MtdFileDescriptor();
+
+  static bool IsMtd(const char* path);
+
+  bool Open(const char* path, int flags, mode_t mode) override;
+  bool Open(const char* path, int flags) override;
+  ssize_t Read(void* buf, size_t count) override;
+  ssize_t Write(const void* buf, size_t count) override;
+  off64_t Seek(off64_t offset, int whence) override;
+  bool Close() override;
+
+ private:
+  std::unique_ptr<MtdReadContext, decltype(&mtd_read_close)> read_ctx_;
+  std::unique_ptr<MtdWriteContext, decltype(&mtd_write_close)> write_ctx_;
+  uint64_t nr_written_;
+};
+
+struct UbiVolumeInfo {
+  // Number of eraseblocks.
+  uint64_t reserved_ebs;
+  // Size of each eraseblock.
+  uint64_t eraseblock_size;
+};
+
+// A file descriptor to update a UBI volume, similar to MtdFileDescriptor.
+// Once the file descriptor is opened for write, the volume is marked as being
+// updated. The volume will not be usable until an update is completed. See
+// UBI_IOCVOLUP ioctl operation.
+class UbiFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+  // Perform some queries about |path| to see if it is a UBI volume.
+  static bool IsUbi(const char* path);
+
+  bool Open(const char* path, int flags, mode_t mode) override;
+  bool Open(const char* path, int flags) override;
+  ssize_t Read(void* buf, size_t count) override;
+  ssize_t Write(const void* buf, size_t count) override;
+  off64_t Seek(off64_t offset, int whence) override;
+  bool Close() override;
+
+ private:
+  enum Mode {
+    kReadOnly,
+    kWriteOnly
+  };
+
+  uint64_t usable_eb_blocks_;
+  uint64_t eraseblock_size_;
+  uint64_t volume_size_;
+  uint64_t nr_written_;
+
+  Mode mode_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MTD_FILE_DESCRIPTOR_H_
diff --git a/multi_range_http_fetcher.cc b/multi_range_http_fetcher.cc
new file mode 100644
index 0000000..07118a3
--- /dev/null
+++ b/multi_range_http_fetcher.cc
@@ -0,0 +1,178 @@
+// Copyright (c) 2010 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/multi_range_http_fetcher.h"
+
+#include <base/strings/stringprintf.h>
+
+#include <algorithm>
+#include <string>
+
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+// Begins the transfer to the specified URL.
+// State change: Stopped -> Downloading
+// (corner case: Stopped -> Stopped for an empty request)
+void MultiRangeHttpFetcher::BeginTransfer(const std::string& url) {
+  CHECK(!base_fetcher_active_) << "BeginTransfer but already active.";
+  CHECK(!pending_transfer_ended_) << "BeginTransfer but pending.";
+  CHECK(!terminating_) << "BeginTransfer but terminating.";
+
+  if (ranges_.empty()) {
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferComplete(this, true);
+    return;
+  }
+  url_ = url;
+  current_index_ = 0;
+  bytes_received_this_range_ = 0;
+  LOG(INFO) << "starting first transfer";
+  base_fetcher_->set_delegate(this);
+  StartTransfer();
+}
+
+// State change: Downloading -> Pending transfer ended
+void MultiRangeHttpFetcher::TerminateTransfer() {
+  if (!base_fetcher_active_) {
+    LOG(INFO) << "Called TerminateTransfer but not active.";
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferTerminated(this);
+    return;
+  }
+  terminating_ = true;
+
+  if (!pending_transfer_ended_) {
+    base_fetcher_->TerminateTransfer();
+  }
+}
+
+// State change: Stopped or Downloading -> Downloading
+void MultiRangeHttpFetcher::StartTransfer() {
+  if (current_index_ >= ranges_.size()) {
+    return;
+  }
+
+  Range range = ranges_[current_index_];
+  LOG(INFO) << "starting transfer of range " << range.ToString();
+
+  bytes_received_this_range_ = 0;
+  base_fetcher_->SetOffset(range.offset());
+  if (range.HasLength())
+    base_fetcher_->SetLength(range.length());
+  else
+    base_fetcher_->UnsetLength();
+  if (delegate_)
+    delegate_->SeekToOffset(range.offset());
+  base_fetcher_active_ = true;
+  base_fetcher_->BeginTransfer(url_);
+}
+
+// State change: Downloading -> Downloading or Pending transfer ended
+void MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher,
+                                          const void* bytes,
+                                          size_t length) {
+  CHECK_LT(current_index_, ranges_.size());
+  CHECK_EQ(fetcher, base_fetcher_.get());
+  CHECK(!pending_transfer_ended_);
+  size_t next_size = length;
+  Range range = ranges_[current_index_];
+  if (range.HasLength()) {
+    next_size = std::min(next_size,
+                         range.length() - bytes_received_this_range_);
+  }
+  LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
+  if (delegate_) {
+    delegate_->ReceivedBytes(this, bytes, next_size);
+  }
+  bytes_received_this_range_ += length;
+  if (range.HasLength() && bytes_received_this_range_ >= range.length()) {
+    // Terminates the current fetcher. Waits for its TransferTerminated
+    // callback before starting the next range so that we don't end up
+    // signalling the delegate that the whole multi-transfer is complete
+    // before all fetchers are really done and cleaned up.
+    pending_transfer_ended_ = true;
+    LOG(INFO) << "terminating transfer";
+    fetcher->TerminateTransfer();
+  }
+}
+
+// State change: Downloading or Pending transfer ended -> Stopped
+void MultiRangeHttpFetcher::TransferEnded(HttpFetcher* fetcher,
+                                          bool successful) {
+  CHECK(base_fetcher_active_) << "Transfer ended unexpectedly.";
+  CHECK_EQ(fetcher, base_fetcher_.get());
+  pending_transfer_ended_ = false;
+  http_response_code_ = fetcher->http_response_code();
+  LOG(INFO) << "TransferEnded w/ code " << http_response_code_;
+  if (terminating_) {
+    LOG(INFO) << "Terminating.";
+    Reset();
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferTerminated(this);
+    return;
+  }
+
+  // If we didn't get enough bytes, it's failure
+  Range range = ranges_[current_index_];
+  if (range.HasLength()) {
+    if (bytes_received_this_range_ < range.length()) {
+      // Failure
+      LOG(INFO) << "Didn't get enough bytes. Ending w/ failure.";
+      Reset();
+      // Note that after the callback returns this object may be destroyed.
+      if (delegate_)
+        delegate_->TransferComplete(this, false);
+      return;
+    }
+    // We got enough bytes and there were bytes specified, so this is success.
+    successful = true;
+  }
+
+  // If we have another transfer, do that.
+  if (current_index_ + 1 < ranges_.size()) {
+    current_index_++;
+    LOG(INFO) << "Starting next transfer (" << current_index_ << ").";
+    StartTransfer();
+    return;
+  }
+
+  LOG(INFO) << "Done w/ all transfers";
+  Reset();
+  // Note that after the callback returns this object may be destroyed.
+  if (delegate_)
+    delegate_->TransferComplete(this, successful);
+}
+
+void MultiRangeHttpFetcher::TransferComplete(HttpFetcher* fetcher,
+                                             bool successful) {
+  LOG(INFO) << "Received transfer complete.";
+  TransferEnded(fetcher, successful);
+}
+
+void MultiRangeHttpFetcher::TransferTerminated(HttpFetcher* fetcher) {
+  LOG(INFO) << "Received transfer terminated.";
+  TransferEnded(fetcher, false);
+}
+
+void MultiRangeHttpFetcher::Reset() {
+  base_fetcher_active_ = pending_transfer_ended_ = terminating_ = false;
+  current_index_ = 0;
+  bytes_received_this_range_ = 0;
+}
+
+std::string MultiRangeHttpFetcher::Range::ToString() const {
+  std::string range_str = base::StringPrintf("%jd+", offset());
+  if (HasLength())
+    base::StringAppendF(&range_str, "%zu", length());
+  else
+    base::StringAppendF(&range_str, "?");
+  return range_str;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/multi_range_http_fetcher.h b/multi_range_http_fetcher.h
new file mode 100644
index 0000000..0b6edfa
--- /dev/null
+++ b/multi_range_http_fetcher.h
@@ -0,0 +1,168 @@
+// Copyright (c) 2010 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_MULTI_RANGE_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/http_fetcher.h"
+
+// This class is a simple wrapper around an HttpFetcher. The client
+// specifies a vector of byte ranges. MultiRangeHttpFetcher will fetch bytes
+// from those offsets, using the same bash fetcher for all ranges. Thus, the
+// fetcher must support beginning a transfer after one has stopped. Pass -1
+// as a length to specify unlimited length. It really only would make sense
+// for the last range specified to have unlimited length, tho it is legal for
+// other entries to have unlimited length.
+
+// There are three states a MultiRangeHttpFetcher object will be in:
+// - Stopped (start state)
+// - Downloading
+// - Pending transfer ended
+// Various functions below that might change state indicate possible
+// state changes.
+
+namespace chromeos_update_engine {
+
+class MultiRangeHttpFetcher : public HttpFetcher, public HttpFetcherDelegate {
+ public:
+  // Takes ownership of the passed in fetcher.
+  explicit MultiRangeHttpFetcher(HttpFetcher* base_fetcher)
+      : HttpFetcher(base_fetcher->proxy_resolver(),
+                    base_fetcher->GetSystemState()),
+        base_fetcher_(base_fetcher),
+        base_fetcher_active_(false),
+        pending_transfer_ended_(false),
+        terminating_(false),
+        current_index_(0),
+        bytes_received_this_range_(0) {}
+  ~MultiRangeHttpFetcher() override {}
+
+  void ClearRanges() { ranges_.clear(); }
+
+  void AddRange(off_t offset, size_t size) {
+    CHECK_GT(size, static_cast<size_t>(0));
+    ranges_.push_back(Range(offset, size));
+  }
+
+  void AddRange(off_t offset) {
+    ranges_.push_back(Range(offset));
+  }
+
+  // HttpFetcher overrides.
+  void SetOffset(off_t offset) override {}  // for now, doesn't support this
+
+  void SetLength(size_t length) override {}  // unsupported
+  void UnsetLength() override {}
+
+  // Begins the transfer to the specified URL.
+  // State change: Stopped -> Downloading
+  // (corner case: Stopped -> Stopped for an empty request)
+  void BeginTransfer(const std::string& url) override;
+
+  // State change: Downloading -> Pending transfer ended
+  void TerminateTransfer() override;
+
+  void Pause() override { base_fetcher_->Pause(); }
+
+  void Unpause() override { base_fetcher_->Unpause(); }
+
+  // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
+  void set_idle_seconds(int seconds) override {
+    base_fetcher_->set_idle_seconds(seconds);
+  }
+  void set_retry_seconds(int seconds) override {
+    base_fetcher_->set_retry_seconds(seconds);
+  }
+  // TODO(deymo): Determine if this method should be virtual in HttpFetcher so
+  // this call is sent to the base_fetcher_.
+  virtual void SetProxies(const std::deque<std::string>& proxies) {
+    base_fetcher_->SetProxies(proxies);
+  }
+
+  inline size_t GetBytesDownloaded() override {
+    return base_fetcher_->GetBytesDownloaded();
+  }
+
+  void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+    base_fetcher_->set_low_speed_limit(low_speed_bps, low_speed_sec);
+  }
+
+  void set_connect_timeout(int connect_timeout_seconds) override {
+    base_fetcher_->set_connect_timeout(connect_timeout_seconds);
+  }
+
+  void set_max_retry_count(int max_retry_count) override {
+    base_fetcher_->set_max_retry_count(max_retry_count);
+  }
+
+ private:
+  // A range object defining the offset and length of a download chunk.  Zero
+  // length indicates an unspecified end offset (note that it is impossible to
+  // request a zero-length range in HTTP).
+  class Range {
+   public:
+    Range(off_t offset, size_t length) : offset_(offset), length_(length) {}
+    explicit Range(off_t offset) : offset_(offset), length_(0) {}
+
+    inline off_t offset() const { return offset_; }
+    inline size_t length() const { return length_; }
+
+    inline bool HasLength() const { return (length_ > 0); }
+
+    std::string ToString() const;
+
+   private:
+    off_t offset_;
+    size_t length_;
+  };
+
+  typedef std::vector<Range> RangesVect;
+
+  // State change: Stopped or Downloading -> Downloading
+  void StartTransfer();
+
+  // HttpFetcherDelegate overrides.
+  // State change: Downloading -> Downloading or Pending transfer ended
+  void ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override;
+
+  // State change: Pending transfer ended -> Stopped
+  void TransferEnded(HttpFetcher* fetcher, bool successful);
+  // These two call TransferEnded():
+  void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+  void TransferTerminated(HttpFetcher* fetcher) override;
+
+  void Reset();
+
+  std::unique_ptr<HttpFetcher> base_fetcher_;
+
+  // If true, do not send any more data or TransferComplete to the delegate.
+  bool base_fetcher_active_;
+
+  // If true, the next fetcher needs to be started when TransferTerminated is
+  // received from the current fetcher.
+  bool pending_transfer_ended_;
+
+  // True if we are waiting for base fetcher to terminate b/c we are
+  // ourselves terminating.
+  bool terminating_;
+
+  RangesVect ranges_;
+
+  RangesVect::size_type current_index_;  // index into ranges_
+  size_t bytes_received_this_range_;
+
+  DISALLOW_COPY_AND_ASSIGN(MultiRangeHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H_
diff --git a/omaha_hash_calculator.cc b/omaha_hash_calculator.cc
new file mode 100644
index 0000000..4a919b8
--- /dev/null
+++ b/omaha_hash_calculator.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2009 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/omaha_hash_calculator.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <chromeos/data_encoding.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+OmahaHashCalculator::OmahaHashCalculator() : valid_(false) {
+  valid_ = (SHA256_Init(&ctx_) == 1);
+  LOG_IF(ERROR, !valid_) << "SHA256_Init failed";
+}
+
+// Update is called with all of the data that should be hashed in order.
+// Mostly just passes the data through to OpenSSL's SHA256_Update()
+bool OmahaHashCalculator::Update(const void* data, size_t length) {
+  TEST_AND_RETURN_FALSE(valid_);
+  TEST_AND_RETURN_FALSE(hash_.empty());
+  static_assert(sizeof(size_t) <= sizeof(unsigned long),  // NOLINT(runtime/int)
+                "length param may be truncated in SHA256_Update");
+  TEST_AND_RETURN_FALSE(SHA256_Update(&ctx_, data, length) == 1);
+  return true;
+}
+
+off_t OmahaHashCalculator::UpdateFile(const string& name, off_t length) {
+  int fd = HANDLE_EINTR(open(name.c_str(), O_RDONLY));
+  if (fd < 0) {
+    return -1;
+  }
+
+  const int kBufferSize = 128 * 1024;  // 128 KiB
+  chromeos::Blob buffer(kBufferSize);
+  off_t bytes_processed = 0;
+  while (length < 0 || bytes_processed < length) {
+    off_t bytes_to_read = buffer.size();
+    if (length >= 0 && bytes_to_read > length - bytes_processed) {
+      bytes_to_read = length - bytes_processed;
+    }
+    ssize_t rc = HANDLE_EINTR(read(fd, buffer.data(), bytes_to_read));
+    if (rc == 0) {  // EOF
+      break;
+    }
+    if (rc < 0 || !Update(buffer.data(), rc)) {
+      bytes_processed = -1;
+      break;
+    }
+    bytes_processed += rc;
+  }
+  IGNORE_EINTR(close(fd));
+  return bytes_processed;
+}
+
+// Call Finalize() when all data has been passed in. This mostly just
+// calls OpenSSL's SHA256_Final() and then base64 encodes the hash.
+bool OmahaHashCalculator::Finalize() {
+  TEST_AND_RETURN_FALSE(hash_.empty());
+  TEST_AND_RETURN_FALSE(raw_hash_.empty());
+  raw_hash_.resize(SHA256_DIGEST_LENGTH);
+  TEST_AND_RETURN_FALSE(SHA256_Final(raw_hash_.data(), &ctx_) == 1);
+
+  // Convert raw_hash_ to base64 encoding and store it in hash_.
+  hash_ = chromeos::data_encoding::Base64Encode(raw_hash_.data(),
+                                                raw_hash_.size());
+  return true;
+}
+
+bool OmahaHashCalculator::RawHashOfBytes(const void* data,
+                                         size_t length,
+                                         chromeos::Blob* out_hash) {
+  OmahaHashCalculator calc;
+  TEST_AND_RETURN_FALSE(calc.Update(data, length));
+  TEST_AND_RETURN_FALSE(calc.Finalize());
+  *out_hash = calc.raw_hash();
+  return true;
+}
+
+bool OmahaHashCalculator::RawHashOfData(const chromeos::Blob& data,
+                                        chromeos::Blob* out_hash) {
+  return RawHashOfBytes(data.data(), data.size(), out_hash);
+}
+
+off_t OmahaHashCalculator::RawHashOfFile(const string& name, off_t length,
+                                         chromeos::Blob* out_hash) {
+  OmahaHashCalculator calc;
+  off_t res = calc.UpdateFile(name, length);
+  if (res < 0) {
+    return res;
+  }
+  if (!calc.Finalize()) {
+    return -1;
+  }
+  *out_hash = calc.raw_hash();
+  return res;
+}
+
+string OmahaHashCalculator::OmahaHashOfBytes(const void* data, size_t length) {
+  OmahaHashCalculator calc;
+  calc.Update(data, length);
+  calc.Finalize();
+  return calc.hash();
+}
+
+string OmahaHashCalculator::OmahaHashOfString(const string& str) {
+  return OmahaHashOfBytes(str.data(), str.size());
+}
+
+string OmahaHashCalculator::OmahaHashOfData(const chromeos::Blob& data) {
+  return OmahaHashOfBytes(data.data(), data.size());
+}
+
+string OmahaHashCalculator::GetContext() const {
+  return string(reinterpret_cast<const char*>(&ctx_), sizeof(ctx_));
+}
+
+bool OmahaHashCalculator::SetContext(const string& context) {
+  TEST_AND_RETURN_FALSE(context.size() == sizeof(ctx_));
+  memcpy(&ctx_, context.data(), sizeof(ctx_));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_hash_calculator.h b/omaha_hash_calculator.h
new file mode 100644
index 0000000..a32ac18
--- /dev/null
+++ b/omaha_hash_calculator.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2009 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_OMAHA_HASH_CALCULATOR_H_
+#define UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H_
+
+#include <openssl/sha.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+// Omaha uses base64 encoded SHA-256 as the hash. This class provides a simple
+// wrapper around OpenSSL providing such a formatted hash of data passed in.
+// The methods of this class must be called in a very specific order: First the
+// ctor (of course), then 0 or more calls to Update(), then Finalize(), then 0
+// or more calls to hash().
+
+namespace chromeos_update_engine {
+
+class OmahaHashCalculator {
+ public:
+  OmahaHashCalculator();
+
+  // Update is called with all of the data that should be hashed in order.
+  // Update will read |length| bytes of |data|.
+  // Returns true on success.
+  bool Update(const void* data, size_t length);
+
+  // Updates the hash with up to |length| bytes of data from |file|. If |length|
+  // is negative, reads in and updates with the whole file. Returns the number
+  // of bytes that the hash was updated with, or -1 on error.
+  off_t UpdateFile(const std::string& name, off_t length);
+
+  // Call Finalize() when all data has been passed in. This method tells
+  // OpenSSl that no more data will come in and base64 encodes the resulting
+  // hash.
+  // Returns true on success.
+  bool Finalize();
+
+  // Gets the hash. Finalize() must have been called.
+  const std::string& hash() const {
+    DCHECK(!hash_.empty()) << "Call Finalize() first";
+    return hash_;
+  }
+
+  const chromeos::Blob& raw_hash() const {
+    DCHECK(!raw_hash_.empty()) << "Call Finalize() first";
+    return raw_hash_;
+  }
+
+  // Gets the current hash context. Note that the string will contain binary
+  // data (including \0 characters).
+  std::string GetContext() const;
+
+  // Sets the current hash context. |context| must the string returned by a
+  // previous OmahaHashCalculator::GetContext method call. Returns true on
+  // success, and false otherwise.
+  bool SetContext(const std::string& context);
+
+  static bool RawHashOfBytes(const void* data,
+                             size_t length,
+                             chromeos::Blob* out_hash);
+  static bool RawHashOfData(const chromeos::Blob& data,
+                            chromeos::Blob* out_hash);
+  static off_t RawHashOfFile(const std::string& name, off_t length,
+                             chromeos::Blob* out_hash);
+
+  // Used by tests
+  static std::string OmahaHashOfBytes(const void* data, size_t length);
+  static std::string OmahaHashOfString(const std::string& str);
+  static std::string OmahaHashOfData(const chromeos::Blob& data);
+
+ private:
+  // If non-empty, the final base64 encoded hash and the raw hash. Will only be
+  // set to non-empty when Finalize is called.
+  std::string hash_;
+  chromeos::Blob raw_hash_;
+
+  // Init success
+  bool valid_;
+
+  // The hash state used by OpenSSL
+  SHA256_CTX ctx_;
+  DISALLOW_COPY_AND_ASSIGN(OmahaHashCalculator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H_
diff --git a/omaha_hash_calculator_unittest.cc b/omaha_hash_calculator_unittest.cc
new file mode 100644
index 0000000..29a0e45
--- /dev/null
+++ b/omaha_hash_calculator_unittest.cc
@@ -0,0 +1,163 @@
+// Copyright (c) 2009 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/omaha_hash_calculator.h"
+
+#include <math.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// Generated by running this on a linux shell:
+// $ echo -n hi | openssl dgst -sha256 -binary | openssl base64
+static const char kExpectedHash[] =
+    "j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ=";
+static const uint8_t kExpectedRawHash[] = {
+  0x8f, 0x43, 0x43, 0x46, 0x64, 0x8f, 0x6b, 0x96,
+  0xdf, 0x89, 0xdd, 0xa9, 0x01, 0xc5, 0x17, 0x6b,
+  0x10, 0xa6, 0xd8, 0x39, 0x61, 0xdd, 0x3c, 0x1a,
+  0xc8, 0x8b, 0x59, 0xb2, 0xdc, 0x32, 0x7a, 0xa4
+};
+
+class OmahaHashCalculatorTest : public ::testing::Test {
+ public:
+  OmahaHashCalculatorTest() {}
+};
+
+TEST_F(OmahaHashCalculatorTest, SimpleTest) {
+  OmahaHashCalculator calc;
+  calc.Update("hi", 2);
+  calc.Finalize();
+  EXPECT_EQ(kExpectedHash, calc.hash());
+  chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+                          std::end(kExpectedRawHash));
+  EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, MultiUpdateTest) {
+  OmahaHashCalculator calc;
+  calc.Update("h", 1);
+  calc.Update("i", 1);
+  calc.Finalize();
+  EXPECT_EQ(kExpectedHash, calc.hash());
+  chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+                          std::end(kExpectedRawHash));
+  EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, ContextTest) {
+  OmahaHashCalculator calc;
+  calc.Update("h", 1);
+  string calc_context = calc.GetContext();
+  calc.Finalize();
+  OmahaHashCalculator calc_next;
+  calc_next.SetContext(calc_context);
+  calc_next.Update("i", 1);
+  calc_next.Finalize();
+  EXPECT_EQ(kExpectedHash, calc_next.hash());
+  chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+                          std::end(kExpectedRawHash));
+  EXPECT_TRUE(raw_hash == calc_next.raw_hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, BigTest) {
+  OmahaHashCalculator calc;
+
+  int digit_count = 1;
+  int next_overflow = 10;
+  for (int i = 0; i < 1000000; i++) {
+    char buf[8];
+    if (i == next_overflow) {
+      next_overflow *= 10;
+      digit_count++;
+    }
+    ASSERT_EQ(digit_count, snprintf(buf, sizeof(buf), "%d", i)) << " i = " << i;
+    calc.Update(buf, strlen(buf));
+  }
+  calc.Finalize();
+
+  // Hash constant generated by running this on a linux shell:
+  // $ C=0
+  // $ while [ $C -lt 1000000 ]; do
+  //     echo -n $C
+  //     let C=C+1
+  //   done | openssl dgst -sha256 -binary | openssl base64
+  EXPECT_EQ("NZf8k6SPBkYMvhaX8YgzuMgbkLP1XZ+neM8K5wcSsf8=", calc.hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, UpdateFileSimpleTest) {
+  string data_path;
+  ASSERT_TRUE(
+      utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+  static const int kLengths[] = { -1, 2, 10 };
+  for (size_t i = 0; i < arraysize(kLengths); i++) {
+    OmahaHashCalculator calc;
+    EXPECT_EQ(2, calc.UpdateFile(data_path, kLengths[i]));
+    EXPECT_TRUE(calc.Finalize());
+    EXPECT_EQ(kExpectedHash, calc.hash());
+    chromeos::Blob raw_hash(std::begin(kExpectedRawHash),
+                            std::end(kExpectedRawHash));
+    EXPECT_TRUE(raw_hash == calc.raw_hash());
+  }
+
+  OmahaHashCalculator calc;
+  EXPECT_EQ(0, calc.UpdateFile(data_path, 0));
+  EXPECT_EQ(1, calc.UpdateFile(data_path, 1));
+  EXPECT_TRUE(calc.Finalize());
+  // echo -n h | openssl dgst -sha256 -binary | openssl base64
+  EXPECT_EQ("qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=", calc.hash());
+}
+
+TEST_F(OmahaHashCalculatorTest, RawHashOfFileSimpleTest) {
+  string data_path;
+  ASSERT_TRUE(
+      utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+  static const int kLengths[] = { -1, 2, 10 };
+  for (size_t i = 0; i < arraysize(kLengths); i++) {
+    chromeos::Blob exp_raw_hash(std::begin(kExpectedRawHash),
+                                std::end(kExpectedRawHash));
+    chromeos::Blob raw_hash;
+    EXPECT_EQ(2, OmahaHashCalculator::RawHashOfFile(data_path,
+                                                    kLengths[i],
+                                                    &raw_hash));
+    EXPECT_TRUE(exp_raw_hash == raw_hash);
+  }
+}
+
+TEST_F(OmahaHashCalculatorTest, UpdateFileNonexistentTest) {
+  OmahaHashCalculator calc;
+  EXPECT_EQ(-1, calc.UpdateFile("/some/non-existent/file", -1));
+}
+
+TEST_F(OmahaHashCalculatorTest, AbortTest) {
+  // Just make sure we don't crash and valgrind doesn't detect memory leaks
+  {
+    OmahaHashCalculator calc;
+  }
+  {
+    OmahaHashCalculator calc;
+    calc.Update("h", 1);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
new file mode 100644
index 0000000..dff8ad3
--- /dev/null
+++ b/omaha_request_action.cc
@@ -0,0 +1,1465 @@
+// Copyright (c) 2012 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/omaha_request_action.h"
+
+#include <inttypes.h>
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <expat.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/real_dbus_wrapper.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// List of custom pair tags that we interpret in the Omaha Response:
+static const char* kTagDeadline = "deadline";
+static const char* kTagDisablePayloadBackoff = "DisablePayloadBackoff";
+static const char* kTagVersion = "version";
+// Deprecated: "IsDelta"
+static const char* kTagIsDeltaPayload = "IsDeltaPayload";
+static const char* kTagMaxFailureCountPerUrl = "MaxFailureCountPerUrl";
+static const char* kTagMaxDaysToScatter = "MaxDaysToScatter";
+// Deprecated: "ManifestSignatureRsa"
+// Deprecated: "ManifestSize"
+static const char* kTagMetadataSignatureRsa = "MetadataSignatureRsa";
+static const char* kTagMetadataSize = "MetadataSize";
+static const char* kTagMoreInfo = "MoreInfo";
+// Deprecated: "NeedsAdmin"
+static const char* kTagPrompt = "Prompt";
+static const char* kTagSha256 = "sha256";
+static const char* kTagDisableP2PForDownloading = "DisableP2PForDownloading";
+static const char* kTagDisableP2PForSharing = "DisableP2PForSharing";
+static const char* kTagPublicKeyRsa = "PublicKeyRsa";
+
+namespace {
+
+static const char* const kGupdateVersion = "ChromeOSUpdateEngine-0.1.0.0";
+
+// Returns an XML ping element attribute assignment with attribute
+// |name| and value |ping_days| if |ping_days| has a value that needs
+// to be sent, or an empty string otherwise.
+string GetPingAttribute(const string& name, int ping_days) {
+  if (ping_days > 0 || ping_days == OmahaRequestAction::kNeverPinged)
+    return base::StringPrintf(" %s=\"%d\"", name.c_str(), ping_days);
+  return "";
+}
+
+// Returns an XML ping element if any of the elapsed days need to be
+// sent, or an empty string otherwise.
+string GetPingXml(int ping_active_days, int ping_roll_call_days) {
+  string ping_active = GetPingAttribute("a", ping_active_days);
+  string ping_roll_call = GetPingAttribute("r", ping_roll_call_days);
+  if (!ping_active.empty() || !ping_roll_call.empty()) {
+    return base::StringPrintf("        <ping active=\"1\"%s%s></ping>\n",
+                              ping_active.c_str(),
+                              ping_roll_call.c_str());
+  }
+  return "";
+}
+
+// Returns an XML that goes into the body of the <app> element of the Omaha
+// request based on the given parameters.
+string GetAppBody(const OmahaEvent* event,
+                  OmahaRequestParams* params,
+                  bool ping_only,
+                  bool include_ping,
+                  int ping_active_days,
+                  int ping_roll_call_days,
+                  PrefsInterface* prefs) {
+  string app_body;
+  if (event == nullptr) {
+    if (include_ping)
+        app_body = GetPingXml(ping_active_days, ping_roll_call_days);
+    if (!ping_only) {
+      app_body += base::StringPrintf(
+          "        <updatecheck targetversionprefix=\"%s\""
+          "></updatecheck>\n",
+          XmlEncodeWithDefault(params->target_version_prefix(), "").c_str());
+
+      // If this is the first update check after a reboot following a previous
+      // update, generate an event containing the previous version number. If
+      // the previous version preference file doesn't exist the event is still
+      // generated with a previous version of 0.0.0.0 -- this is relevant for
+      // older clients or new installs. The previous version event is not sent
+      // for ping-only requests because they come before the client has
+      // rebooted.
+      string prev_version;
+      if (!prefs->GetString(kPrefsPreviousVersion, &prev_version)) {
+        prev_version = "0.0.0.0";
+      }
+
+      app_body += base::StringPrintf(
+          "        <event eventtype=\"%d\" eventresult=\"%d\" "
+          "previousversion=\"%s\"></event>\n",
+          OmahaEvent::kTypeUpdateComplete,
+          OmahaEvent::kResultSuccessReboot,
+          XmlEncodeWithDefault(prev_version, "0.0.0.0").c_str());
+      LOG_IF(WARNING, !prefs->SetString(kPrefsPreviousVersion, ""))
+          << "Unable to reset the previous version.";
+    }
+  } else {
+    // The error code is an optional attribute so append it only if the result
+    // is not success.
+    string error_code;
+    if (event->result != OmahaEvent::kResultSuccess) {
+      error_code = base::StringPrintf(" errorcode=\"%d\"",
+                                      static_cast<int>(event->error_code));
+    }
+    app_body = base::StringPrintf(
+        "        <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
+        event->type, event->result, error_code.c_str());
+  }
+
+  return app_body;
+}
+
+// Returns the cohort* argument to include in the <app> tag for the passed
+// |arg_name| and |prefs_key|, if any. The return value is suitable to
+// concatenate to the list of arguments and includes a space at the end.
+string GetCohortArgXml(PrefsInterface* prefs,
+                       const string arg_name,
+                       const string prefs_key) {
+  // There's nothing wrong with not having a given cohort setting, so we check
+  // existance first to avoid the warning log message.
+  if (!prefs->Exists(prefs_key))
+    return "";
+  string cohort_value;
+  if (!prefs->GetString(prefs_key, &cohort_value) || cohort_value.empty())
+    return "";
+  // This is a sanity check to avoid sending a huge XML file back to Ohama due
+  // to a compromised stateful partition making the update check fail in low
+  // network environments envent after a reboot.
+  if (cohort_value.size() > 1024) {
+    LOG(WARNING) << "The omaha cohort setting " << arg_name
+                 << " has a too big value, which must be an error or an "
+                    "attacker trying to inhibit updates.";
+    return "";
+  }
+
+  string escaped_xml_value;
+  if (!XmlEncode(cohort_value, &escaped_xml_value)) {
+    LOG(WARNING) << "The omaha cohort setting " << arg_name
+                 << " is ASCII-7 invalid, ignoring it.";
+    return "";
+  }
+
+  return base::StringPrintf("%s=\"%s\" ",
+                            arg_name.c_str(), escaped_xml_value.c_str());
+}
+
+// Returns an XML that corresponds to the entire <app> node of the Omaha
+// request based on the given parameters.
+string GetAppXml(const OmahaEvent* event,
+                 OmahaRequestParams* params,
+                 bool ping_only,
+                 bool include_ping,
+                 int ping_active_days,
+                 int ping_roll_call_days,
+                 int install_date_in_days,
+                 SystemState* system_state) {
+  string app_body = GetAppBody(event, params, ping_only, include_ping,
+                               ping_active_days, ping_roll_call_days,
+                               system_state->prefs());
+  string app_versions;
+
+  // If we are upgrading to a more stable channel and we are allowed to do
+  // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
+  // highest-versioned payload on the destination channel.
+  if (params->to_more_stable_channel() && params->is_powerwash_allowed()) {
+    LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
+              << "on downgrading to the version in the more stable channel";
+    app_versions = "version=\"0.0.0.0\" from_version=\"" +
+        XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+  } else {
+    app_versions = "version=\"" +
+        XmlEncodeWithDefault(params->app_version(), "0.0.0.0") + "\" ";
+  }
+
+  string download_channel = params->download_channel();
+  string app_channels =
+      "track=\"" + XmlEncodeWithDefault(download_channel, "") + "\" ";
+  if (params->current_channel() != download_channel) {
+    app_channels += "from_track=\"" + XmlEncodeWithDefault(
+        params->current_channel(), "") + "\" ";
+  }
+
+  string delta_okay_str = params->delta_okay() ? "true" : "false";
+
+  // If install_date_days is not set (e.g. its value is -1 ), don't
+  // include the attribute.
+  string install_date_in_days_str = "";
+  if (install_date_in_days >= 0) {
+    install_date_in_days_str = base::StringPrintf("installdate=\"%d\" ",
+                                                  install_date_in_days);
+  }
+
+  string app_cohort_args;
+  app_cohort_args += GetCohortArgXml(system_state->prefs(),
+                                     "cohort", kPrefsOmahaCohort);
+  app_cohort_args += GetCohortArgXml(system_state->prefs(),
+                                     "cohorthint", kPrefsOmahaCohortHint);
+  app_cohort_args += GetCohortArgXml(system_state->prefs(),
+                                     "cohortname", kPrefsOmahaCohortName);
+
+  string app_xml = "    <app "
+      "appid=\"" + XmlEncodeWithDefault(params->GetAppId(), "") + "\" " +
+      app_cohort_args +
+      app_versions +
+      app_channels +
+      "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " +
+      "board=\"" + XmlEncodeWithDefault(params->os_board(), "") + "\" " +
+      "hardware_class=\"" + XmlEncodeWithDefault(params->hwid(), "") + "\" " +
+      "delta_okay=\"" + delta_okay_str + "\" "
+      "fw_version=\"" + XmlEncodeWithDefault(params->fw_version(), "") + "\" " +
+      "ec_version=\"" + XmlEncodeWithDefault(params->ec_version(), "") + "\" " +
+      install_date_in_days_str +
+      ">\n" +
+         app_body +
+      "    </app>\n";
+
+  return app_xml;
+}
+
+// Returns an XML that corresponds to the entire <os> node of the Omaha
+// request based on the given parameters.
+string GetOsXml(OmahaRequestParams* params) {
+  string os_xml ="    <os "
+      "version=\"" + XmlEncodeWithDefault(params->os_version(), "") + "\" " +
+      "platform=\"" + XmlEncodeWithDefault(params->os_platform(), "") + "\" " +
+      "sp=\"" + XmlEncodeWithDefault(params->os_sp(), "") + "\">"
+      "</os>\n";
+  return os_xml;
+}
+
+// Returns an XML that corresponds to the entire Omaha request based on the
+// given parameters.
+string GetRequestXml(const OmahaEvent* event,
+                     OmahaRequestParams* params,
+                     bool ping_only,
+                     bool include_ping,
+                     int ping_active_days,
+                     int ping_roll_call_days,
+                     int install_date_in_days,
+                     SystemState* system_state) {
+  string os_xml = GetOsXml(params);
+  string app_xml = GetAppXml(event, params, ping_only, include_ping,
+                             ping_active_days, ping_roll_call_days,
+                             install_date_in_days, system_state);
+
+  string install_source = base::StringPrintf("installsource=\"%s\" ",
+      (params->interactive() ? "ondemandupdate" : "scheduler"));
+
+  string request_xml =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      "<request protocol=\"3.0\" " + (
+          "version=\"" + XmlEncodeWithDefault(kGupdateVersion, "") + "\" "
+          "updaterversion=\"" + XmlEncodeWithDefault(kGupdateVersion,
+                                                     "") + "\" " +
+          install_source +
+          "ismachine=\"1\">\n") +
+      os_xml +
+      app_xml +
+      "</request>\n";
+
+  return request_xml;
+}
+
+}  // namespace
+
+// Struct used for holding data obtained when parsing the XML.
+struct OmahaParserData {
+  explicit OmahaParserData(XML_Parser _xml_parser) : xml_parser(_xml_parser) {}
+
+  // Pointer to the expat XML_Parser object.
+  XML_Parser xml_parser;
+
+  // This is the state of the parser as it's processing the XML.
+  bool failed = false;
+  bool entity_decl = false;
+  string current_path;
+
+  // These are the values extracted from the XML.
+  string app_cohort;
+  string app_cohorthint;
+  string app_cohortname;
+  bool app_cohort_set = false;
+  bool app_cohorthint_set = false;
+  bool app_cohortname_set = false;
+  string updatecheck_status;
+  string updatecheck_poll_interval;
+  string daystart_elapsed_days;
+  string daystart_elapsed_seconds;
+  vector<string> url_codebase;
+  string package_name;
+  string package_size;
+  string manifest_version;
+  map<string, string> action_postinstall_attrs;
+};
+
+namespace {
+
+// Callback function invoked by expat.
+void ParserHandlerStart(void* user_data, const XML_Char* element,
+                        const XML_Char** attr) {
+  OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+  if (data->failed)
+    return;
+
+  data->current_path += string("/") + element;
+
+  map<string, string> attrs;
+  if (attr != nullptr) {
+    for (int n = 0; attr[n] != nullptr && attr[n+1] != nullptr; n += 2) {
+      string key = attr[n];
+      string value = attr[n + 1];
+      attrs[key] = value;
+    }
+  }
+
+  if (data->current_path == "/response/app") {
+    if (attrs.find("cohort") != attrs.end()) {
+      data->app_cohort_set = true;
+      data->app_cohort = attrs["cohort"];
+    }
+    if (attrs.find("cohorthint") != attrs.end()) {
+      data->app_cohorthint_set = true;
+      data->app_cohorthint = attrs["cohorthint"];
+    }
+    if (attrs.find("cohortname") != attrs.end()) {
+      data->app_cohortname_set = true;
+      data->app_cohortname = attrs["cohortname"];
+    }
+  } else if (data->current_path == "/response/app/updatecheck") {
+    // There is only supposed to be a single <updatecheck> element.
+    data->updatecheck_status = attrs["status"];
+    data->updatecheck_poll_interval = attrs["PollInterval"];
+  } else if (data->current_path == "/response/daystart") {
+    // Get the install-date.
+    data->daystart_elapsed_days = attrs["elapsed_days"];
+    data->daystart_elapsed_seconds = attrs["elapsed_seconds"];
+  } else if (data->current_path == "/response/app/updatecheck/urls/url") {
+    // Look at all <url> elements.
+    data->url_codebase.push_back(attrs["codebase"]);
+  } else if (data->package_name.empty() && data->current_path ==
+             "/response/app/updatecheck/manifest/packages/package") {
+    // Only look at the first <package>.
+    data->package_name = attrs["name"];
+    data->package_size = attrs["size"];
+  } else if (data->current_path == "/response/app/updatecheck/manifest") {
+    // Get the version.
+    data->manifest_version = attrs[kTagVersion];
+  } else if (data->current_path ==
+             "/response/app/updatecheck/manifest/actions/action") {
+    // We only care about the postinstall action.
+    if (attrs["event"] == "postinstall") {
+      data->action_postinstall_attrs = attrs;
+    }
+  }
+}
+
+// Callback function invoked by expat.
+void ParserHandlerEnd(void* user_data, const XML_Char* element) {
+  OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+  if (data->failed)
+    return;
+
+  const string path_suffix = string("/") + element;
+
+  if (!base::EndsWith(data->current_path, path_suffix, true)) {
+    LOG(ERROR) << "Unexpected end element '" << element
+               << "' with current_path='" << data->current_path << "'";
+    data->failed = true;
+    return;
+  }
+  data->current_path.resize(data->current_path.size() - path_suffix.size());
+}
+
+// Callback function invoked by expat.
+//
+// This is called for entity declarations. Since Omaha is guaranteed
+// to never return any XML with entities our course of action is to
+// just stop parsing. This avoids potential resource exhaustion
+// problems AKA the "billion laughs". CVE-2013-0340.
+void ParserHandlerEntityDecl(void *user_data,
+                             const XML_Char *entity_name,
+                             int is_parameter_entity,
+                             const XML_Char *value,
+                             int value_length,
+                             const XML_Char *base,
+                             const XML_Char *system_id,
+                             const XML_Char *public_id,
+                             const XML_Char *notation_name) {
+  OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+  LOG(ERROR) << "XML entities are not supported. Aborting parsing.";
+  data->failed = true;
+  data->entity_decl = true;
+  XML_StopParser(data->xml_parser, false);
+}
+
+}  // namespace
+
+bool XmlEncode(const string& input, string* output) {
+  if (std::find_if(input.begin(), input.end(),
+                   [](const char c){return c & 0x80;}) != input.end()) {
+    LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:";
+    utils::HexDumpString(input);
+    return false;
+  }
+  output->clear();
+  // We need at least input.size() space in the output, but the code below will
+  // handle it if we need more.
+  output->reserve(input.size());
+  for (char c : input) {
+    switch (c) {
+      case '\"':
+        output->append("&quot;");
+        break;
+      case '\'':
+        output->append("&apos;");
+        break;
+      case '&':
+        output->append("&amp;");
+        break;
+      case '<':
+        output->append("&lt;");
+        break;
+      case '>':
+        output->append("&gt;");
+        break;
+      default:
+        output->push_back(c);
+    }
+  }
+  return true;
+}
+
+string XmlEncodeWithDefault(const string& input, const string& default_value) {
+  string output;
+  if (XmlEncode(input, &output))
+    return output;
+  return default_value;
+}
+
+OmahaRequestAction::OmahaRequestAction(SystemState* system_state,
+                                       OmahaEvent* event,
+                                       HttpFetcher* http_fetcher,
+                                       bool ping_only)
+    : system_state_(system_state),
+      event_(event),
+      http_fetcher_(http_fetcher),
+      ping_only_(ping_only),
+      ping_active_days_(0),
+      ping_roll_call_days_(0) {
+  params_ = system_state->request_params();
+}
+
+OmahaRequestAction::~OmahaRequestAction() {}
+
+// Calculates the value to use for the ping days parameter.
+int OmahaRequestAction::CalculatePingDays(const string& key) {
+  int days = kNeverPinged;
+  int64_t last_ping = 0;
+  if (system_state_->prefs()->GetInt64(key, &last_ping) && last_ping >= 0) {
+    days = (Time::Now() - Time::FromInternalValue(last_ping)).InDays();
+    if (days < 0) {
+      // If |days| is negative, then the system clock must have jumped
+      // back in time since the ping was sent. Mark the value so that
+      // it doesn't get sent to the server but we still update the
+      // last ping daystart preference. This way the next ping time
+      // will be correct, hopefully.
+      days = kPingTimeJump;
+      LOG(WARNING) <<
+          "System clock jumped back in time. Resetting ping daystarts.";
+    }
+  }
+  return days;
+}
+
+void OmahaRequestAction::InitPingDays() {
+  // We send pings only along with update checks, not with events.
+  if (IsEvent()) {
+    return;
+  }
+  // TODO(petkov): Figure a way to distinguish active use pings
+  // vs. roll call pings. Currently, the two pings are identical. A
+  // fix needs to change this code as well as UpdateLastPingDays and ShouldPing.
+  ping_active_days_ = CalculatePingDays(kPrefsLastActivePingDay);
+  ping_roll_call_days_ = CalculatePingDays(kPrefsLastRollCallPingDay);
+}
+
+bool OmahaRequestAction::ShouldPing() const {
+  if (ping_active_days_ == OmahaRequestAction::kNeverPinged &&
+      ping_roll_call_days_ == OmahaRequestAction::kNeverPinged) {
+    int powerwash_count = system_state_->hardware()->GetPowerwashCount();
+    if (powerwash_count > 0) {
+      LOG(INFO) << "Not sending ping with a=-1 r=-1 to omaha because "
+                << "powerwash_count is " << powerwash_count;
+      return false;
+    }
+    return true;
+  }
+  return ping_active_days_ > 0 || ping_roll_call_days_ > 0;
+}
+
+// static
+int OmahaRequestAction::GetInstallDate(SystemState* system_state) {
+  PrefsInterface* prefs = system_state->prefs();
+  if (prefs == nullptr)
+    return -1;
+
+  // If we have the value stored on disk, just return it.
+  int64_t stored_value;
+  if (prefs->GetInt64(kPrefsInstallDateDays, &stored_value)) {
+    // Convert and sanity-check.
+    int install_date_days = static_cast<int>(stored_value);
+    if (install_date_days >= 0)
+      return install_date_days;
+    LOG(ERROR) << "Dropping stored Omaha InstallData since its value num_days="
+               << install_date_days << " looks suspicious.";
+    prefs->Delete(kPrefsInstallDateDays);
+  }
+
+  // Otherwise, if OOBE is not complete then do nothing and wait for
+  // ParseResponse() to call ParseInstallDate() and then
+  // PersistInstallDate() to set the kPrefsInstallDateDays state
+  // variable. Once that is done, we'll then report back in future
+  // Omaha requests.  This works exactly because OOBE triggers an
+  // update check.
+  //
+  // However, if OOBE is complete and the kPrefsInstallDateDays state
+  // variable is not set, there are two possibilities
+  //
+  //   1. The update check in OOBE failed so we never got a response
+  //      from Omaha (no network etc.); or
+  //
+  //   2. OOBE was done on an older version that didn't write to the
+  //      kPrefsInstallDateDays state variable.
+  //
+  // In both cases, we approximate the install date by simply
+  // inspecting the timestamp of when OOBE happened.
+
+  Time time_of_oobe;
+  if (!system_state->hardware()->IsOOBEComplete(&time_of_oobe)) {
+    LOG(INFO) << "Not generating Omaha InstallData as we have "
+              << "no prefs file and OOBE is not complete.";
+    return -1;
+  }
+
+  int num_days;
+  if (!utils::ConvertToOmahaInstallDate(time_of_oobe, &num_days)) {
+    LOG(ERROR) << "Not generating Omaha InstallData from time of OOBE "
+               << "as its value '" << utils::ToString(time_of_oobe)
+               << "' looks suspicious.";
+    return -1;
+  }
+
+  // Persist this to disk, for future use.
+  if (!OmahaRequestAction::PersistInstallDate(system_state,
+                                              num_days,
+                                              kProvisionedFromOOBEMarker))
+    return -1;
+
+  LOG(INFO) << "Set the Omaha InstallDate from OOBE time-stamp to "
+            << num_days << " days";
+
+  return num_days;
+}
+
+void OmahaRequestAction::PerformAction() {
+  http_fetcher_->set_delegate(this);
+  InitPingDays();
+  if (ping_only_ && !ShouldPing()) {
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+    return;
+  }
+
+  string request_post(GetRequestXml(event_.get(),
+                                    params_,
+                                    ping_only_,
+                                    ShouldPing(),  // include_ping
+                                    ping_active_days_,
+                                    ping_roll_call_days_,
+                                    GetInstallDate(system_state_),
+                                    system_state_));
+
+  http_fetcher_->SetPostData(request_post.data(), request_post.size(),
+                             kHttpContentTypeTextXml);
+  LOG(INFO) << "Posting an Omaha request to " << params_->update_url();
+  LOG(INFO) << "Request: " << request_post;
+  http_fetcher_->BeginTransfer(params_->update_url());
+}
+
+void OmahaRequestAction::TerminateProcessing() {
+  http_fetcher_->TerminateTransfer();
+}
+
+// We just store the response in the buffer. Once we've received all bytes,
+// we'll look in the buffer and decide what to do.
+void OmahaRequestAction::ReceivedBytes(HttpFetcher *fetcher,
+                                       const void* bytes,
+                                       size_t length) {
+  const uint8_t* byte_ptr = reinterpret_cast<const uint8_t*>(bytes);
+  response_buffer_.insert(response_buffer_.end(), byte_ptr, byte_ptr + length);
+}
+
+namespace {
+
+// Parses a 64 bit base-10 int from a string and returns it. Returns 0
+// on error. If the string contains "0", that's indistinguishable from
+// error.
+off_t ParseInt(const string& str) {
+  off_t ret = 0;
+  int rc = sscanf(str.c_str(), "%" PRIi64, &ret);  // NOLINT(runtime/printf)
+  if (rc < 1) {
+    // failure
+    return 0;
+  }
+  return ret;
+}
+
+// Parses |str| and returns |true| if, and only if, its value is "true".
+bool ParseBool(const string& str) {
+  return str == "true";
+}
+
+// Update the last ping day preferences based on the server daystart
+// response. Returns true on success, false otherwise.
+bool UpdateLastPingDays(OmahaParserData *parser_data, PrefsInterface* prefs) {
+  int64_t elapsed_seconds = 0;
+  TEST_AND_RETURN_FALSE(
+      base::StringToInt64(parser_data->daystart_elapsed_seconds,
+                          &elapsed_seconds));
+  TEST_AND_RETURN_FALSE(elapsed_seconds >= 0);
+
+  // Remember the local time that matches the server's last midnight
+  // time.
+  Time daystart = Time::Now() - TimeDelta::FromSeconds(elapsed_seconds);
+  prefs->SetInt64(kPrefsLastActivePingDay, daystart.ToInternalValue());
+  prefs->SetInt64(kPrefsLastRollCallPingDay, daystart.ToInternalValue());
+  return true;
+}
+}  // namespace
+
+bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
+                                       OmahaResponse* output_object,
+                                       ScopedActionCompleter* completer) {
+  if (parser_data->updatecheck_status.empty()) {
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  // chromium-os:37289: The PollInterval is not supported by Omaha server
+  // currently.  But still keeping this existing code in case we ever decide to
+  // slow down the request rate from the server-side. Note that the PollInterval
+  // is not persisted, so it has to be sent by the server on every response to
+  // guarantee that the scheduler uses this value (otherwise, if the device got
+  // rebooted after the last server-indicated value, it'll revert to the default
+  // value). Also kDefaultMaxUpdateChecks value for the scattering logic is
+  // based on the assumption that we perform an update check every hour so that
+  // the max value of 8 will roughly be equivalent to one work day. If we decide
+  // to use PollInterval permanently, we should update the
+  // max_update_checks_allowed to take PollInterval into account.  Note: The
+  // parsing for PollInterval happens even before parsing of the status because
+  // we may want to specify the PollInterval even when there's no update.
+  base::StringToInt(parser_data->updatecheck_poll_interval,
+                    &output_object->poll_interval);
+
+  // Check for the "elapsed_days" attribute in the "daystart"
+  // element. This is the number of days since Jan 1 2007, 0:00
+  // PST. If we don't have a persisted value of the Omaha InstallDate,
+  // we'll use it to calculate it and then persist it.
+  if (ParseInstallDate(parser_data, output_object) &&
+      !HasInstallDate(system_state_)) {
+    // Since output_object->install_date_days is never negative, the
+    // elapsed_days -> install-date calculation is reduced to simply
+    // rounding down to the nearest number divisible by 7.
+    int remainder = output_object->install_date_days % 7;
+    int install_date_days_rounded =
+        output_object->install_date_days - remainder;
+    if (PersistInstallDate(system_state_,
+                           install_date_days_rounded,
+                           kProvisionedFromOmahaResponse)) {
+      LOG(INFO) << "Set the Omaha InstallDate from Omaha Response to "
+                << install_date_days_rounded << " days";
+    }
+  }
+
+  // We persist the cohorts sent by omaha even if the status is "noupdate".
+  if (parser_data->app_cohort_set)
+    PersistCohortData(kPrefsOmahaCohort, parser_data->app_cohort);
+  if (parser_data->app_cohorthint_set)
+    PersistCohortData(kPrefsOmahaCohortHint, parser_data->app_cohorthint);
+  if (parser_data->app_cohortname_set)
+    PersistCohortData(kPrefsOmahaCohortName, parser_data->app_cohortname);
+
+  if (!ParseStatus(parser_data, output_object, completer))
+    return false;
+
+  // Note: ParseUrls MUST be called before ParsePackage as ParsePackage
+  // appends the package name to the URLs populated in this method.
+  if (!ParseUrls(parser_data, output_object, completer))
+    return false;
+
+  if (!ParsePackage(parser_data, output_object, completer))
+    return false;
+
+  if (!ParseParams(parser_data, output_object, completer))
+    return false;
+
+  return true;
+}
+
+bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data,
+                                     OmahaResponse* output_object,
+                                     ScopedActionCompleter* completer) {
+  const string& status = parser_data->updatecheck_status;
+  if (status == "noupdate") {
+    LOG(INFO) << "No update.";
+    output_object->update_exists = false;
+    SetOutputObject(*output_object);
+    completer->set_code(ErrorCode::kSuccess);
+    return false;
+  }
+
+  if (status != "ok") {
+    LOG(ERROR) << "Unknown Omaha response status: " << status;
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  return true;
+}
+
+bool OmahaRequestAction::ParseUrls(OmahaParserData* parser_data,
+                                   OmahaResponse* output_object,
+                                   ScopedActionCompleter* completer) {
+  if (parser_data->url_codebase.empty()) {
+    LOG(ERROR) << "No Omaha Response URLs";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  LOG(INFO) << "Found " << parser_data->url_codebase.size() << " url(s)";
+  output_object->payload_urls.clear();
+  for (const auto& codebase : parser_data->url_codebase) {
+    if (codebase.empty()) {
+      LOG(ERROR) << "Omaha Response URL has empty codebase";
+      completer->set_code(ErrorCode::kOmahaResponseInvalid);
+      return false;
+    }
+    output_object->payload_urls.push_back(codebase);
+  }
+
+  return true;
+}
+
+bool OmahaRequestAction::ParsePackage(OmahaParserData* parser_data,
+                                      OmahaResponse* output_object,
+                                      ScopedActionCompleter* completer) {
+  if (parser_data->package_name.empty()) {
+    LOG(ERROR) << "Omaha Response has empty package name";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  // Append the package name to each URL in our list so that we don't
+  // propagate the urlBase vs packageName distinctions beyond this point.
+  // From now on, we only need to use payload_urls.
+  for (auto& payload_url : output_object->payload_urls)
+    payload_url += parser_data->package_name;
+
+  // Parse the payload size.
+  off_t size = ParseInt(parser_data->package_size);
+  if (size <= 0) {
+    LOG(ERROR) << "Omaha Response has invalid payload size: " << size;
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+  output_object->size = size;
+
+  LOG(INFO) << "Payload size = " << output_object->size << " bytes";
+
+  return true;
+}
+
+bool OmahaRequestAction::ParseParams(OmahaParserData* parser_data,
+                                     OmahaResponse* output_object,
+                                     ScopedActionCompleter* completer) {
+  output_object->version = parser_data->manifest_version;
+  if (output_object->version.empty()) {
+    LOG(ERROR) << "Omaha Response does not have version in manifest!";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  LOG(INFO) << "Received omaha response to update to version "
+            << output_object->version;
+
+  map<string, string> attrs = parser_data->action_postinstall_attrs;
+  if (attrs.empty()) {
+    LOG(ERROR) << "Omaha Response has no postinstall event action";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  output_object->hash = attrs[kTagSha256];
+  if (output_object->hash.empty()) {
+    LOG(ERROR) << "Omaha Response has empty sha256 value";
+    completer->set_code(ErrorCode::kOmahaResponseInvalid);
+    return false;
+  }
+
+  // Get the optional properties one by one.
+  output_object->more_info_url = attrs[kTagMoreInfo];
+  output_object->metadata_size = ParseInt(attrs[kTagMetadataSize]);
+  output_object->metadata_signature = attrs[kTagMetadataSignatureRsa];
+  output_object->prompt = ParseBool(attrs[kTagPrompt]);
+  output_object->deadline = attrs[kTagDeadline];
+  output_object->max_days_to_scatter = ParseInt(attrs[kTagMaxDaysToScatter]);
+  output_object->disable_p2p_for_downloading =
+      ParseBool(attrs[kTagDisableP2PForDownloading]);
+  output_object->disable_p2p_for_sharing =
+      ParseBool(attrs[kTagDisableP2PForSharing]);
+  output_object->public_key_rsa = attrs[kTagPublicKeyRsa];
+
+  string max = attrs[kTagMaxFailureCountPerUrl];
+  if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
+    output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl;
+
+  output_object->is_delta_payload = ParseBool(attrs[kTagIsDeltaPayload]);
+
+  output_object->disable_payload_backoff =
+      ParseBool(attrs[kTagDisablePayloadBackoff]);
+
+  return true;
+}
+
+// If the transfer was successful, this uses expat to parse the response
+// and fill in the appropriate fields of the output object. Also, notifies
+// the processor that we're done.
+void OmahaRequestAction::TransferComplete(HttpFetcher *fetcher,
+                                          bool successful) {
+  ScopedActionCompleter completer(processor_, this);
+  string current_response(response_buffer_.begin(), response_buffer_.end());
+  LOG(INFO) << "Omaha request response: " << current_response;
+
+  PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+  // Events are best effort transactions -- assume they always succeed.
+  if (IsEvent()) {
+    CHECK(!HasOutputPipe()) << "No output pipe allowed for event requests.";
+    if (event_->result == OmahaEvent::kResultError && successful &&
+        system_state_->hardware()->IsOfficialBuild()) {
+      LOG(INFO) << "Signalling Crash Reporter.";
+      utils::ScheduleCrashReporterUpload();
+    }
+    completer.set_code(ErrorCode::kSuccess);
+    return;
+  }
+
+  if (!successful) {
+    LOG(ERROR) << "Omaha request network transfer failed.";
+    int code = GetHTTPResponseCode();
+    // Makes sure we send sane error values.
+    if (code < 0 || code >= 1000) {
+      code = 999;
+    }
+    completer.set_code(static_cast<ErrorCode>(
+        static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + code));
+    return;
+  }
+
+  XML_Parser parser = XML_ParserCreate(nullptr);
+  OmahaParserData parser_data(parser);
+  XML_SetUserData(parser, &parser_data);
+  XML_SetElementHandler(parser, ParserHandlerStart, ParserHandlerEnd);
+  XML_SetEntityDeclHandler(parser, ParserHandlerEntityDecl);
+  XML_Status res = XML_Parse(
+      parser,
+      reinterpret_cast<const char*>(response_buffer_.data()),
+      response_buffer_.size(),
+      XML_TRUE);
+  XML_ParserFree(parser);
+
+  if (res != XML_STATUS_OK || parser_data.failed) {
+    LOG(ERROR) << "Omaha response not valid XML: "
+               << XML_ErrorString(XML_GetErrorCode(parser))
+               << " at line " << XML_GetCurrentLineNumber(parser)
+               << " col " << XML_GetCurrentColumnNumber(parser);
+    ErrorCode error_code = ErrorCode::kOmahaRequestXMLParseError;
+    if (response_buffer_.empty()) {
+      error_code = ErrorCode::kOmahaRequestEmptyResponseError;
+    } else if (parser_data.entity_decl) {
+      error_code = ErrorCode::kOmahaRequestXMLHasEntityDecl;
+    }
+    completer.set_code(error_code);
+    return;
+  }
+
+  // Update the last ping day preferences based on the server daystart response
+  // even if we didn't send a ping. Omaha always includes the daystart in the
+  // response, but log the error if it didn't.
+  LOG_IF(ERROR, !UpdateLastPingDays(&parser_data, system_state_->prefs()))
+      << "Failed to update the last ping day preferences!";
+
+  if (!HasOutputPipe()) {
+    // Just set success to whether or not the http transfer succeeded,
+    // which must be true at this point in the code.
+    completer.set_code(ErrorCode::kSuccess);
+    return;
+  }
+
+  OmahaResponse output_object;
+  if (!ParseResponse(&parser_data, &output_object, &completer))
+    return;
+  output_object.update_exists = true;
+  SetOutputObject(output_object);
+
+  if (ShouldIgnoreUpdate(output_object)) {
+    output_object.update_exists = false;
+    completer.set_code(ErrorCode::kOmahaUpdateIgnoredPerPolicy);
+    return;
+  }
+
+  // If Omaha says to disable p2p, respect that
+  if (output_object.disable_p2p_for_downloading) {
+    LOG(INFO) << "Forcibly disabling use of p2p for downloading as "
+              << "requested by Omaha.";
+    payload_state->SetUsingP2PForDownloading(false);
+  }
+  if (output_object.disable_p2p_for_sharing) {
+    LOG(INFO) << "Forcibly disabling use of p2p for sharing as "
+              << "requested by Omaha.";
+    payload_state->SetUsingP2PForSharing(false);
+  }
+
+  // Update the payload state with the current response. The payload state
+  // will automatically reset all stale state if this response is different
+  // from what's stored already. We are updating the payload state as late
+  // as possible in this method so that if a new release gets pushed and then
+  // got pulled back due to some issues, we don't want to clear our internal
+  // state unnecessarily.
+  payload_state->SetResponse(output_object);
+
+  // It could be we've already exceeded the deadline for when p2p is
+  // allowed or that we've tried too many times with p2p. Check that.
+  if (payload_state->GetUsingP2PForDownloading()) {
+    payload_state->P2PNewAttempt();
+    if (!payload_state->P2PAttemptAllowed()) {
+      LOG(INFO) << "Forcibly disabling use of p2p for downloading because "
+                << "of previous failures when using p2p.";
+      payload_state->SetUsingP2PForDownloading(false);
+    }
+  }
+
+  // From here on, we'll complete stuff in CompleteProcessing() so
+  // disable |completer| since we'll create a new one in that
+  // function.
+  completer.set_should_complete(false);
+
+  // If we're allowed to use p2p for downloading we do not pay
+  // attention to wall-clock-based waiting if the URL is indeed
+  // available via p2p. Therefore, check if the file is available via
+  // p2p before deferring...
+  if (payload_state->GetUsingP2PForDownloading()) {
+    LookupPayloadViaP2P(output_object);
+  } else {
+    CompleteProcessing();
+  }
+}
+
+void OmahaRequestAction::CompleteProcessing() {
+  ScopedActionCompleter completer(processor_, this);
+  OmahaResponse& output_object = const_cast<OmahaResponse&>(GetOutputObject());
+  PayloadStateInterface* payload_state = system_state_->payload_state();
+
+  if (ShouldDeferDownload(&output_object)) {
+    output_object.update_exists = false;
+    LOG(INFO) << "Ignoring Omaha updates as updates are deferred by policy.";
+    completer.set_code(ErrorCode::kOmahaUpdateDeferredPerPolicy);
+    return;
+  }
+
+  if (payload_state->ShouldBackoffDownload()) {
+    output_object.update_exists = false;
+    LOG(INFO) << "Ignoring Omaha updates in order to backoff our retry "
+              << "attempts";
+    completer.set_code(ErrorCode::kOmahaUpdateDeferredForBackoff);
+    return;
+  }
+  completer.set_code(ErrorCode::kSuccess);
+}
+
+void OmahaRequestAction::OnLookupPayloadViaP2PCompleted(const string& url) {
+  LOG(INFO) << "Lookup complete, p2p-client returned URL '" << url << "'";
+  if (!url.empty()) {
+    system_state_->payload_state()->SetP2PUrl(url);
+  } else {
+    LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+              << "because no suitable peer could be found.";
+    system_state_->payload_state()->SetUsingP2PForDownloading(false);
+  }
+  CompleteProcessing();
+}
+
+void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) {
+  // If the device is in the middle of an update, the state variables
+  // kPrefsUpdateStateNextDataOffset, kPrefsUpdateStateNextDataLength
+  // tracks the offset and length of the operation currently in
+  // progress. The offset is based from the end of the manifest which
+  // is kPrefsManifestMetadataSize bytes long.
+  //
+  // To make forward progress and avoid deadlocks, we need to find a
+  // peer that has at least the entire operation we're currently
+  // working on. Otherwise we may end up in a situation where two
+  // devices bounce back and forth downloading from each other,
+  // neither making any forward progress until one of them decides to
+  // stop using p2p (via kMaxP2PAttempts and kMaxP2PAttemptTimeSeconds
+  // safe-guards). See http://crbug.com/297170 for an example)
+  size_t minimum_size = 0;
+  int64_t manifest_metadata_size = 0;
+  int64_t next_data_offset = 0;
+  int64_t next_data_length = 0;
+  if (system_state_ &&
+      system_state_->prefs()->GetInt64(kPrefsManifestMetadataSize,
+                                       &manifest_metadata_size) &&
+      manifest_metadata_size != -1 &&
+      system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataOffset,
+                                       &next_data_offset) &&
+      next_data_offset != -1 &&
+      system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataLength,
+                                       &next_data_length)) {
+    minimum_size = manifest_metadata_size + next_data_offset + next_data_length;
+  }
+
+  string file_id = utils::CalculateP2PFileId(response.hash, response.size);
+  if (system_state_->p2p_manager()) {
+    LOG(INFO) << "Checking if payload is available via p2p, file_id="
+              << file_id << " minimum_size=" << minimum_size;
+    system_state_->p2p_manager()->LookupUrlForFile(
+        file_id,
+        minimum_size,
+        TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds),
+        base::Bind(&OmahaRequestAction::OnLookupPayloadViaP2PCompleted,
+                   base::Unretained(this)));
+  }
+}
+
+bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) {
+  if (params_->interactive()) {
+    LOG(INFO) << "Not deferring download because update is interactive.";
+    return false;
+  }
+
+  // If we're using p2p to download _and_ we have a p2p URL, we never
+  // defer the download. This is because the download will always
+  // happen from a peer on the LAN and we've been waiting in line for
+  // our turn.
+  const PayloadStateInterface* payload_state = system_state_->payload_state();
+  if (payload_state->GetUsingP2PForDownloading() &&
+      !payload_state->GetP2PUrl().empty()) {
+    LOG(INFO) << "Download not deferred because download "
+              << "will happen from a local peer (via p2p).";
+    return false;
+  }
+
+  // We should defer the downloads only if we've first satisfied the
+  // wall-clock-based-waiting period and then the update-check-based waiting
+  // period, if required.
+  if (!params_->wall_clock_based_wait_enabled()) {
+    LOG(INFO) << "Wall-clock-based waiting period is not enabled,"
+              << " so no deferring needed.";
+    return false;
+  }
+
+  switch (IsWallClockBasedWaitingSatisfied(output_object)) {
+    case kWallClockWaitNotSatisfied:
+      // We haven't even satisfied the first condition, passing the
+      // wall-clock-based waiting period, so we should defer the downloads
+      // until that happens.
+      LOG(INFO) << "wall-clock-based-wait not satisfied.";
+      return true;
+
+    case kWallClockWaitDoneButUpdateCheckWaitRequired:
+      LOG(INFO) << "wall-clock-based-wait satisfied and "
+                << "update-check-based-wait required.";
+      return !IsUpdateCheckCountBasedWaitingSatisfied();
+
+    case kWallClockWaitDoneAndUpdateCheckWaitNotRequired:
+      // Wall-clock-based waiting period is satisfied, and it's determined
+      // that we do not need the update-check-based wait. so no need to
+      // defer downloads.
+      LOG(INFO) << "wall-clock-based-wait satisfied and "
+                << "update-check-based-wait is not required.";
+      return false;
+
+    default:
+      // Returning false for this default case so we err on the
+      // side of downloading updates than deferring in case of any bugs.
+      NOTREACHED();
+      return false;
+  }
+}
+
+OmahaRequestAction::WallClockWaitResult
+OmahaRequestAction::IsWallClockBasedWaitingSatisfied(
+    OmahaResponse* output_object) {
+  Time update_first_seen_at;
+  int64_t update_first_seen_at_int;
+
+  if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) {
+    if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt,
+                                         &update_first_seen_at_int)) {
+      // Note: This timestamp could be that of ANY update we saw in the past
+      // (not necessarily this particular update we're considering to apply)
+      // but never got to apply because of some reason (e.g. stop AU policy,
+      // updates being pulled out from Omaha, changes in target version prefix,
+      // new update being rolled out, etc.). But for the purposes of scattering
+      // it doesn't matter which update the timestamp corresponds to. i.e.
+      // the clock starts ticking the first time we see an update and we're
+      // ready to apply when the random wait period is satisfied relative to
+      // that first seen timestamp.
+      update_first_seen_at = Time::FromInternalValue(update_first_seen_at_int);
+      LOG(INFO) << "Using persisted value of UpdateFirstSeenAt: "
+                << utils::ToString(update_first_seen_at);
+    } else {
+      // This seems like an unexpected error where the persisted value exists
+      // but it's not readable for some reason. Just skip scattering in this
+      // case to be safe.
+     LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read";
+     return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+    }
+  } else {
+    update_first_seen_at = Time::Now();
+    update_first_seen_at_int = update_first_seen_at.ToInternalValue();
+    if (system_state_->prefs()->SetInt64(kPrefsUpdateFirstSeenAt,
+                                         update_first_seen_at_int)) {
+      LOG(INFO) << "Persisted the new value for UpdateFirstSeenAt: "
+                << utils::ToString(update_first_seen_at);
+    } else {
+      // This seems like an unexpected error where the value cannot be
+      // persisted for some reason. Just skip scattering in this
+      // case to be safe.
+      LOG(INFO) << "Not scattering as UpdateFirstSeenAt value "
+                << utils::ToString(update_first_seen_at)
+                << " cannot be persisted";
+     return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+    }
+  }
+
+  TimeDelta elapsed_time = Time::Now() - update_first_seen_at;
+  TimeDelta max_scatter_period = TimeDelta::FromDays(
+      output_object->max_days_to_scatter);
+
+  LOG(INFO) << "Waiting Period = "
+            << utils::FormatSecs(params_->waiting_period().InSeconds())
+            << ", Time Elapsed = "
+            << utils::FormatSecs(elapsed_time.InSeconds())
+            << ", MaxDaysToScatter = "
+            << max_scatter_period.InDays();
+
+  if (!output_object->deadline.empty()) {
+    // The deadline is set for all rules which serve a delta update from a
+    // previous FSI, which means this update will be applied mostly in OOBE
+    // cases. For these cases, we shouldn't scatter so as to finish the OOBE
+    // quickly.
+    LOG(INFO) << "Not scattering as deadline flag is set";
+    return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+  }
+
+  if (max_scatter_period.InDays() == 0) {
+    // This means the Omaha rule creator decides that this rule
+    // should not be scattered irrespective of the policy.
+    LOG(INFO) << "Not scattering as MaxDaysToScatter in rule is 0.";
+    return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+  }
+
+  if (elapsed_time > max_scatter_period) {
+    // This means we've waited more than the upperbound wait in the rule
+    // from the time we first saw a valid update available to us.
+    // This will prevent update starvation.
+    LOG(INFO) << "Not scattering as we're past the MaxDaysToScatter limit.";
+    return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+  }
+
+  // This means we are required to participate in scattering.
+  // See if our turn has arrived now.
+  TimeDelta remaining_wait_time = params_->waiting_period() - elapsed_time;
+  if (remaining_wait_time.InSeconds() <= 0) {
+    // Yes, it's our turn now.
+    LOG(INFO) << "Successfully passed the wall-clock-based-wait.";
+
+    // But we can't download until the update-check-count-based wait is also
+    // satisfied, so mark it as required now if update checks are enabled.
+    return params_->update_check_count_wait_enabled() ?
+              kWallClockWaitDoneButUpdateCheckWaitRequired :
+              kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+  }
+
+  // Not our turn yet, so we have to wait until our turn to
+  // help scatter the downloads across all clients of the enterprise.
+  LOG(INFO) << "Update deferred for another "
+            << utils::FormatSecs(remaining_wait_time.InSeconds())
+            << " per policy.";
+  return kWallClockWaitNotSatisfied;
+}
+
+bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() {
+  int64_t update_check_count_value;
+
+  if (system_state_->prefs()->Exists(kPrefsUpdateCheckCount)) {
+    if (!system_state_->prefs()->GetInt64(kPrefsUpdateCheckCount,
+                                          &update_check_count_value)) {
+      // We are unable to read the update check count from file for some reason.
+      // So let's proceed anyway so as to not stall the update.
+      LOG(ERROR) << "Unable to read update check count. "
+                 << "Skipping update-check-count-based-wait.";
+      return true;
+    }
+  } else {
+    // This file does not exist. This means we haven't started our update
+    // check count down yet, so this is the right time to start the count down.
+    update_check_count_value = base::RandInt(
+      params_->min_update_checks_needed(),
+      params_->max_update_checks_allowed());
+
+    LOG(INFO) << "Randomly picked update check count value = "
+              << update_check_count_value;
+
+    // Write out the initial value of update_check_count_value.
+    if (!system_state_->prefs()->SetInt64(kPrefsUpdateCheckCount,
+                                          update_check_count_value)) {
+      // We weren't able to write the update check count file for some reason.
+      // So let's proceed anyway so as to not stall the update.
+      LOG(ERROR) << "Unable to write update check count. "
+                 << "Skipping update-check-count-based-wait.";
+      return true;
+    }
+  }
+
+  if (update_check_count_value == 0) {
+    LOG(INFO) << "Successfully passed the update-check-based-wait.";
+    return true;
+  }
+
+  if (update_check_count_value < 0 ||
+      update_check_count_value > params_->max_update_checks_allowed()) {
+    // We err on the side of skipping scattering logic instead of stalling
+    // a machine from receiving any updates in case of any unexpected state.
+    LOG(ERROR) << "Invalid value for update check count detected. "
+               << "Skipping update-check-count-based-wait.";
+    return true;
+  }
+
+  // Legal value, we need to wait for more update checks to happen
+  // until this becomes 0.
+  LOG(INFO) << "Deferring Omaha updates for another "
+            << update_check_count_value
+            << " update checks per policy";
+  return false;
+}
+
+// static
+bool OmahaRequestAction::ParseInstallDate(OmahaParserData* parser_data,
+                                          OmahaResponse* output_object) {
+  int64_t elapsed_days = 0;
+  if (!base::StringToInt64(parser_data->daystart_elapsed_days,
+                           &elapsed_days))
+    return false;
+
+  if (elapsed_days < 0)
+    return false;
+
+  output_object->install_date_days = elapsed_days;
+  return true;
+}
+
+// static
+bool OmahaRequestAction::HasInstallDate(SystemState *system_state) {
+  PrefsInterface* prefs = system_state->prefs();
+  if (prefs == nullptr)
+    return false;
+
+  return prefs->Exists(kPrefsInstallDateDays);
+}
+
+// static
+bool OmahaRequestAction::PersistInstallDate(
+    SystemState *system_state,
+    int install_date_days,
+    InstallDateProvisioningSource source) {
+  TEST_AND_RETURN_FALSE(install_date_days >= 0);
+
+  PrefsInterface* prefs = system_state->prefs();
+  if (prefs == nullptr)
+    return false;
+
+  if (!prefs->SetInt64(kPrefsInstallDateDays, install_date_days))
+    return false;
+
+  string metric_name = "Installer.InstallDateProvisioningSource";
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric_name,
+      static_cast<int>(source),  // Sample.
+      kProvisionedMax);          // Maximum.
+
+  metric_name = metrics::kMetricInstallDateProvisioningSource;
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric_name,
+      static_cast<int>(source),  // Sample.
+      kProvisionedMax);          // Maximum.
+
+  return true;
+}
+
+bool OmahaRequestAction::PersistCohortData(
+    const string& prefs_key,
+    const string& new_value) {
+  if (new_value.empty() && system_state_->prefs()->Exists(prefs_key)) {
+    LOG(INFO) << "Removing stored " << prefs_key << " value.";
+    return system_state_->prefs()->Delete(prefs_key);
+  } else if (!new_value.empty()) {
+    LOG(INFO) << "Storing new setting " << prefs_key << " as " << new_value;
+    return system_state_->prefs()->SetString(prefs_key, new_value);
+  }
+  return true;
+}
+
+void OmahaRequestAction::ActionCompleted(ErrorCode code) {
+  // We only want to report this on "update check".
+  if (ping_only_ || event_ != nullptr)
+    return;
+
+  metrics::CheckResult result = metrics::CheckResult::kUnset;
+  metrics::CheckReaction reaction = metrics::CheckReaction::kUnset;
+  metrics::DownloadErrorCode download_error_code =
+      metrics::DownloadErrorCode::kUnset;
+
+  // Regular update attempt.
+  switch (code) {
+  case ErrorCode::kSuccess:
+    // OK, we parsed the response successfully but that does
+    // necessarily mean that an update is available.
+    if (HasOutputPipe()) {
+      const OmahaResponse& response = GetOutputObject();
+      if (response.update_exists) {
+        result = metrics::CheckResult::kUpdateAvailable;
+        reaction = metrics::CheckReaction::kUpdating;
+      } else {
+        result = metrics::CheckResult::kNoUpdateAvailable;
+      }
+    } else {
+      result = metrics::CheckResult::kNoUpdateAvailable;
+    }
+    break;
+
+  case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    result = metrics::CheckResult::kUpdateAvailable;
+    reaction = metrics::CheckReaction::kIgnored;
+    break;
+
+  case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    result = metrics::CheckResult::kUpdateAvailable;
+    reaction = metrics::CheckReaction::kDeferring;
+    break;
+
+  case ErrorCode::kOmahaUpdateDeferredForBackoff:
+    result = metrics::CheckResult::kUpdateAvailable;
+    reaction = metrics::CheckReaction::kBackingOff;
+    break;
+
+  default:
+    // We report two flavors of errors, "Download errors" and "Parsing
+    // error". Try to convert to the former and if that doesn't work
+    // we know it's the latter.
+    metrics::DownloadErrorCode tmp_error = utils::GetDownloadErrorCode(code);
+    if (tmp_error != metrics::DownloadErrorCode::kInputMalformed) {
+      result = metrics::CheckResult::kDownloadError;
+      download_error_code = tmp_error;
+    } else {
+      result = metrics::CheckResult::kParsingError;
+    }
+    break;
+  }
+
+  metrics::ReportUpdateCheckMetrics(system_state_,
+                                    result, reaction, download_error_code);
+}
+
+bool OmahaRequestAction::ShouldIgnoreUpdate(
+    const OmahaResponse& response) const {
+  // Note: policy decision to not update to a version we rolled back from.
+  string rollback_version =
+      system_state_->payload_state()->GetRollbackVersion();
+  if (!rollback_version.empty()) {
+    LOG(INFO) << "Detected previous rollback from version " << rollback_version;
+    if (rollback_version == response.version) {
+      LOG(INFO) << "Received version that we rolled back from. Ignoring.";
+      return true;
+    }
+  }
+
+  if (!IsUpdateAllowedOverCurrentConnection()) {
+    LOG(INFO) << "Update is not allowed over current connection.";
+    return true;
+  }
+
+  // Note: We could technically delete the UpdateFirstSeenAt state when we
+  // return true. If we do, it'll mean a device has to restart the
+  // UpdateFirstSeenAt and thus help scattering take effect when the AU is
+  // turned on again. On the other hand, it also increases the chance of update
+  // starvation if an admin turns AU on/off more frequently. We choose to err on
+  // the side of preventing starvation at the cost of not applying scattering in
+  // those cases.
+  return false;
+}
+
+bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection() const {
+  NetworkConnectionType type;
+  NetworkTethering tethering;
+  RealDBusWrapper dbus_iface;
+  ConnectionManager* connection_manager = system_state_->connection_manager();
+  if (!connection_manager->GetConnectionProperties(&dbus_iface,
+                                                   &type, &tethering)) {
+    LOG(INFO) << "We could not determine our connection type. "
+              << "Defaulting to allow updates.";
+    return true;
+  }
+  bool is_allowed = connection_manager->IsUpdateAllowedOver(type, tethering);
+  LOG(INFO) << "We are connected via "
+            << connection_manager->StringForConnectionType(type)
+            << ", Updates allowed: " << (is_allowed ? "Yes" : "No");
+  return is_allowed;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_request_action.h b/omaha_request_action.h
new file mode 100644
index 0000000..b56af1e
--- /dev/null
+++ b/omaha_request_action.h
@@ -0,0 +1,315 @@
+// Copyright (c) 2012 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_OMAHA_REQUEST_ACTION_H_
+#define UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <curl/curl.h>
+
+#include "update_engine/action.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/omaha_response.h"
+
+// The Omaha Request action makes a request to Omaha and can output
+// the response on the output ActionPipe.
+
+namespace chromeos_update_engine {
+
+// Encodes XML entities in a given string. Input must be ASCII-7 valid. If
+// the input is invalid, the default value is used instead.
+std::string XmlEncodeWithDefault(const std::string& input,
+                                 const std::string& default_value);
+
+// Escapes text so it can be included as character data and attribute
+// values. The |input| string must be valid ASCII-7, no UTF-8 supported.
+// Returns whether the |input| was valid and escaped properly in |output|.
+bool XmlEncode(const std::string& input, std::string* output);
+
+// This struct encapsulates the Omaha event information. For a
+// complete list of defined event types and results, see
+// http://code.google.com/p/omaha/wiki/ServerProtocol#event
+struct OmahaEvent {
+  // The Type values correspond to EVENT_TYPE values of Omaha.
+  enum Type {
+    kTypeUnknown = 0,
+    kTypeDownloadComplete = 1,
+    kTypeInstallComplete = 2,
+    kTypeUpdateComplete = 3,
+    kTypeUpdateDownloadStarted = 13,
+    kTypeUpdateDownloadFinished = 14,
+  };
+
+  // The Result values correspond to EVENT_RESULT values of Omaha.
+  enum Result {
+    kResultError = 0,
+    kResultSuccess = 1,
+    kResultSuccessReboot = 2,
+    kResultUpdateDeferred = 9,  // When we ignore/defer updates due to policy.
+  };
+
+  OmahaEvent()
+      : type(kTypeUnknown),
+        result(kResultError),
+        error_code(ErrorCode::kError) {}
+  explicit OmahaEvent(Type in_type)
+      : type(in_type),
+        result(kResultSuccess),
+        error_code(ErrorCode::kSuccess) {}
+  OmahaEvent(Type in_type, Result in_result, ErrorCode in_error_code)
+      : type(in_type),
+        result(in_result),
+        error_code(in_error_code) {}
+
+  Type type;
+  Result result;
+  ErrorCode error_code;
+};
+
+class NoneType;
+class OmahaRequestAction;
+class OmahaRequestParams;
+class PrefsInterface;
+
+// This struct is declared in the .cc file.
+struct OmahaParserData;
+
+template<>
+class ActionTraits<OmahaRequestAction> {
+ public:
+  // Takes parameters on the input pipe.
+  typedef NoneType InputObjectType;
+  // On UpdateCheck success, puts the Omaha response on output. Event
+  // requests do not have an output pipe.
+  typedef OmahaResponse OutputObjectType;
+};
+
+class OmahaRequestAction : public Action<OmahaRequestAction>,
+                           public HttpFetcherDelegate {
+ public:
+  static const int kNeverPinged = -1;
+  static const int kPingTimeJump = -2;
+  // We choose this value of 10 as a heuristic for a work day in trying
+  // each URL, assuming we check roughly every 45 mins. This is a good time to
+  // wait - neither too long nor too little - so we don't give up the preferred
+  // URLs that appear earlier in list too quickly before moving on to the
+  // fallback ones.
+  static const int kDefaultMaxFailureCountPerUrl = 10;
+
+  // These are the possible outcome upon checking whether we satisfied
+  // the wall-clock-based-wait.
+  enum WallClockWaitResult {
+    kWallClockWaitNotSatisfied,
+    kWallClockWaitDoneButUpdateCheckWaitRequired,
+    kWallClockWaitDoneAndUpdateCheckWaitNotRequired,
+  };
+
+  // The ctor takes in all the parameters that will be used for making
+  // the request to Omaha. For some of them we have constants that
+  // should be used.
+  //
+  // Takes ownership of the passed in HttpFetcher. Useful for testing.
+  //
+  // Takes ownership of the passed in OmahaEvent. If |event| is null,
+  // this is an UpdateCheck request, otherwise it's an Event request.
+  // Event requests always succeed.
+  //
+  // A good calling pattern is:
+  // OmahaRequestAction(..., new OmahaEvent(...), new WhateverHttpFetcher);
+  // or
+  // OmahaRequestAction(..., nullptr, new WhateverHttpFetcher);
+  OmahaRequestAction(SystemState* system_state,
+                     OmahaEvent* event,
+                     HttpFetcher* http_fetcher,
+                     bool ping_only);
+  ~OmahaRequestAction() override;
+  typedef ActionTraits<OmahaRequestAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<OmahaRequestAction>::OutputObjectType OutputObjectType;
+  void PerformAction() override;
+  void TerminateProcessing() override;
+  void ActionCompleted(ErrorCode code) override;
+
+  int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+  // Debugging/logging
+  static std::string StaticType() { return "OmahaRequestAction"; }
+  std::string Type() const override { return StaticType(); }
+
+  // Delegate methods (see http_fetcher.h)
+  void ReceivedBytes(HttpFetcher *fetcher,
+                     const void* bytes, size_t length) override;
+
+  void TransferComplete(HttpFetcher *fetcher, bool successful) override;
+
+  // Returns true if this is an Event request, false if it's an UpdateCheck.
+  bool IsEvent() const { return event_.get() != nullptr; }
+
+ private:
+  FRIEND_TEST(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE);
+  FRIEND_TEST(OmahaRequestActionTest,
+              GetInstallDateWhenOOBECompletedWithInvalidDate);
+  FRIEND_TEST(OmahaRequestActionTest,
+              GetInstallDateWhenOOBECompletedWithValidDate);
+  FRIEND_TEST(OmahaRequestActionTest,
+              GetInstallDateWhenOOBECompletedDateChanges);
+
+  // Enumeration used in PersistInstallDate().
+  enum InstallDateProvisioningSource {
+    kProvisionedFromOmahaResponse,
+    kProvisionedFromOOBEMarker,
+
+    // kProvisionedMax is the count of the number of enums above. Add
+    // any new enums above this line only.
+    kProvisionedMax
+  };
+
+  // Gets the install date, expressed as the number of PST8PDT
+  // calendar weeks since January 1st 2007, times seven. Returns -1 if
+  // unknown. See http://crbug.com/336838 for details about this value.
+  static int GetInstallDate(SystemState* system_state);
+
+  // Parses the Omaha Response in |doc| and sets the
+  // |install_date_days| field of |output_object| to the value of the
+  // elapsed_days attribute of the daystart element. Returns True if
+  // the value was set, False if it wasn't found.
+  static bool ParseInstallDate(OmahaParserData* parser_data,
+                               OmahaResponse* output_object);
+
+  // Returns True if the kPrefsInstallDateDays state variable is set,
+  // False otherwise.
+  static bool HasInstallDate(SystemState *system_state);
+
+  // Writes |install_date_days| into the kPrefsInstallDateDays state
+  // variable and emits an UMA stat for the |source| used. Returns
+  // True if the value was written, False if an error occurred.
+  static bool PersistInstallDate(SystemState *system_state,
+                                 int install_date_days,
+                                 InstallDateProvisioningSource source);
+
+  // Persist the new cohort* value received in the XML file in the |prefs_key|
+  // preference file. If the |new_value| is empty, the currently stored value
+  // will be deleted. Don't call this function with an empty |new_value| if the
+  // value was not set in the XML, since that would delete the stored value.
+  bool PersistCohortData(const std::string& prefs_key,
+                         const std::string& new_value);
+
+  // If this is an update check request, initializes
+  // |ping_active_days_| and |ping_roll_call_days_| to values that may
+  // be sent as pings to Omaha.
+  void InitPingDays();
+
+  // Based on the persistent preference store values, calculates the
+  // number of days since the last ping sent for |key|.
+  int CalculatePingDays(const std::string& key);
+
+  // Returns whether we have "active_days" or "roll_call_days" ping values to
+  // send to Omaha and thus we should include them in the response.
+  bool ShouldPing() const;
+
+  // Returns true if the download of a new update should be deferred.
+  // False if the update can be downloaded.
+  bool ShouldDeferDownload(OmahaResponse* output_object);
+
+  // Returns true if the basic wall-clock-based waiting period has been
+  // satisfied based on the scattering policy setting. False otherwise.
+  // If true, it also indicates whether the additional update-check-count-based
+  // waiting period also needs to be satisfied before the download can begin.
+  WallClockWaitResult IsWallClockBasedWaitingSatisfied(
+      OmahaResponse* output_object);
+
+  // Returns true if the update-check-count-based waiting period has been
+  // satisfied. False otherwise.
+  bool IsUpdateCheckCountBasedWaitingSatisfied();
+
+  // Parses the response from Omaha that's available in |doc| using the other
+  // helper methods below and populates the |output_object| with the relevant
+  // values. Returns true if we should continue the parsing.  False otherwise,
+  // in which case it sets any error code using |completer|.
+  bool ParseResponse(OmahaParserData* parser_data,
+                     OmahaResponse* output_object,
+                     ScopedActionCompleter* completer);
+
+  // Parses the status property in the given update_check_node and populates
+  // |output_object| if valid. Returns true if we should continue the parsing.
+  // False otherwise, in which case it sets any error code using |completer|.
+  bool ParseStatus(OmahaParserData* parser_data,
+                   OmahaResponse* output_object,
+                   ScopedActionCompleter* completer);
+
+  // Parses the URL nodes in the given XML document and populates
+  // |output_object| if valid. Returns true if we should continue the parsing.
+  // False otherwise, in which case it sets any error code using |completer|.
+  bool ParseUrls(OmahaParserData* parser_data,
+                 OmahaResponse* output_object,
+                 ScopedActionCompleter* completer);
+
+  // Parses the package node in the given XML document and populates
+  // |output_object| if valid. Returns true if we should continue the parsing.
+  // False otherwise, in which case it sets any error code using |completer|.
+  bool ParsePackage(OmahaParserData* parser_data,
+                    OmahaResponse* output_object,
+                    ScopedActionCompleter* completer);
+
+  // Parses the other parameters in the given XML document and populates
+  // |output_object| if valid. Returns true if we should continue the parsing.
+  // False otherwise, in which case it sets any error code using |completer|.
+  bool ParseParams(OmahaParserData* parser_data,
+                   OmahaResponse* output_object,
+                   ScopedActionCompleter* completer);
+
+  // Called by TransferComplete() to complete processing, either
+  // asynchronously after looking up resources via p2p or directly.
+  void CompleteProcessing();
+
+  // Helper to asynchronously look up payload on the LAN.
+  void LookupPayloadViaP2P(const OmahaResponse& response);
+
+  // Callback used by LookupPayloadViaP2P().
+  void OnLookupPayloadViaP2PCompleted(const std::string& url);
+
+  // Returns true if the current update should be ignored.
+  bool ShouldIgnoreUpdate(const OmahaResponse& response) const;
+
+  // Returns true if updates are allowed over the current type of connection.
+  // False otherwise.
+  bool IsUpdateAllowedOverCurrentConnection() const;
+
+  // Global system context.
+  SystemState* system_state_;
+
+  // Contains state that is relevant in the processing of the Omaha request.
+  OmahaRequestParams* params_;
+
+  // Pointer to the OmahaEvent info. This is an UpdateCheck request if null.
+  std::unique_ptr<OmahaEvent> event_;
+
+  // pointer to the HttpFetcher that does the http work
+  std::unique_ptr<HttpFetcher> http_fetcher_;
+
+  // If true, only include the <ping> element in the request.
+  bool ping_only_;
+
+  // Stores the response from the omaha server
+  chromeos::Blob response_buffer_;
+
+  // Initialized by InitPingDays to values that may be sent to Omaha
+  // as part of a ping message. Note that only positive values and -1
+  // are sent to Omaha.
+  int ping_active_days_;
+  int ping_roll_call_days_;
+
+  DISALLOW_COPY_AND_ASSIGN(OmahaRequestAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
new file mode 100644
index 0000000..1131c9c
--- /dev/null
+++ b/omaha_request_action_unittest.cc
@@ -0,0 +1,2117 @@
+// Copyright (c) 2012 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/omaha_request_action.h"
+
+#include <glib.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/constants.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::vector;
+using testing::AllOf;
+using testing::AnyNumber;
+using testing::DoAll;
+using testing::Ge;
+using testing::Le;
+using testing::NiceMock;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace {
+
+// This is a helper struct to allow unit tests build an update response with the
+// values they care about.
+struct FakeUpdateResponse {
+  string GetNoUpdateResponse() const {
+    string entity_str;
+    if (include_entity)
+      entity_str = "<!DOCTYPE response [<!ENTITY CrOS \"ChromeOS\">]>";
+    return
+        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+        entity_str + "<response protocol=\"3.0\">"
+        "<daystart elapsed_seconds=\"100\"/>"
+        "<app appid=\"" + app_id + "\" " +
+        (include_cohorts ? "cohort=\"" + cohort + "\" cohorthint=\"" +
+         cohorthint + "\" cohortname=\"" + cohortname + "\" " : "") +
+        " status=\"ok\">"
+        "<ping status=\"ok\"/>"
+        "<updatecheck status=\"noupdate\"/></app></response>";
+  }
+
+  string GetUpdateResponse() const {
+    return
+        "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+        "protocol=\"3.0\">"
+        "<daystart elapsed_seconds=\"100\"" +
+        (elapsed_days.empty() ? "" : (" elapsed_days=\"" + elapsed_days + "\""))
+        + "/>"
+        "<app appid=\"" + app_id + "\" " +
+        (include_cohorts ? "cohort=\"" + cohort + "\" cohorthint=\"" +
+         cohorthint + "\" cohortname=\"" + cohortname + "\" " : "") +
+        " status=\"ok\">"
+        "<ping status=\"ok\"/><updatecheck status=\"ok\">"
+        "<urls><url codebase=\"" + codebase + "\"/></urls>"
+        "<manifest version=\"" + version + "\">"
+        "<packages><package hash=\"not-used\" name=\"" + filename +  "\" "
+        "size=\"" + base::Int64ToString(size) + "\"/></packages>"
+        "<actions><action event=\"postinstall\" "
+        "ChromeOSVersion=\"" + version + "\" "
+        "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
+        "IsDelta=\"true\" "
+        "IsDeltaPayload=\"true\" "
+        "MaxDaysToScatter=\"" + max_days_to_scatter + "\" "
+        "sha256=\"" + hash + "\" "
+        "needsadmin=\"" + needsadmin + "\" " +
+        (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
+        (disable_p2p_for_downloading ?
+            "DisableP2PForDownloading=\"true\" " : "") +
+        (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") +
+        "/></actions></manifest></updatecheck></app></response>";
+  }
+
+  // Return the payload URL, which is split in two fields in the XML response.
+  string GetPayloadUrl() {
+    return codebase + filename;
+  }
+
+  string app_id = chromeos_update_engine::OmahaRequestParams::kAppId;
+  string version = "1.2.3.4";
+  string more_info_url = "http://more/info";
+  string prompt = "true";
+  string codebase = "http://code/base/";
+  string filename = "file.signed";
+  string hash = "HASH1234=";
+  string needsadmin = "false";
+  int64_t size = 123;
+  string deadline = "";
+  string max_days_to_scatter = "7";
+  string elapsed_days = "42";
+
+  // P2P setting defaults to allowed.
+  bool disable_p2p_for_downloading = false;
+  bool disable_p2p_for_sharing = false;
+
+  // Omaha cohorts settings.
+  bool include_cohorts = false;
+  string cohort = "";
+  string cohorthint = "";
+  string cohortname = "";
+
+  // Whether to include the CrOS <!ENTITY> in the XML response.
+  bool include_entity = false;
+};
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+class OmahaRequestActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    fake_system_state_.set_request_params(&request_params_);
+    fake_system_state_.set_prefs(&fake_prefs_);
+  }
+
+  // Returns true iff an output response was obtained from the
+  // OmahaRequestAction. |prefs| may be null, in which case a local MockPrefs
+  // is used. |payload_state| may be null, in which case a local mock is used.
+  // |p2p_manager| may be null, in which case a local mock is used.
+  // |connection_manager| may be null, in which case a local mock is used.
+  // out_response may be null. If |fail_http_response_code| is non-negative,
+  // the transfer will fail with that code. |ping_only| is passed through to the
+  // OmahaRequestAction constructor. out_post_data may be null; if non-null, the
+  // post-data received by the mock HttpFetcher is returned.
+  //
+  // The |expected_check_result|, |expected_check_reaction| and
+  // |expected_error_code| parameters are for checking expectations
+  // about reporting UpdateEngine.Check.{Result,Reaction,DownloadError}
+  // UMA statistics. Use the appropriate ::kUnset value to specify that
+  // the given metric should not be reported.
+  bool TestUpdateCheck(OmahaRequestParams* request_params,
+                       const string& http_response,
+                       int fail_http_response_code,
+                       bool ping_only,
+                       ErrorCode expected_code,
+                       metrics::CheckResult expected_check_result,
+                       metrics::CheckReaction expected_check_reaction,
+                       metrics::DownloadErrorCode expected_download_error_code,
+                       OmahaResponse* out_response,
+                       chromeos::Blob* out_post_data);
+
+  // Runs and checks a ping test. |ping_only| indicates whether it should send
+  // only a ping or also an updatecheck.
+  void PingTest(bool ping_only);
+
+  // InstallDate test helper function.
+  bool InstallDateParseHelper(const string &elapsed_days,
+                              OmahaResponse *response);
+
+  // P2P test helper function.
+  void P2PTest(
+      bool initial_allow_p2p_for_downloading,
+      bool initial_allow_p2p_for_sharing,
+      bool omaha_disable_p2p_for_downloading,
+      bool omaha_disable_p2p_for_sharing,
+      bool payload_state_allow_p2p_attempt,
+      bool expect_p2p_client_lookup,
+      const string& p2p_client_result_url,
+      bool expected_allow_p2p_for_downloading,
+      bool expected_allow_p2p_for_sharing,
+      const string& expected_p2p_url);
+
+  FakeSystemState fake_system_state_;
+  FakeUpdateResponse fake_update_response_;
+
+  // By default, all tests use these objects unless they replace them in the
+  // fake_system_state_.
+  OmahaRequestParams request_params_ = OmahaRequestParams{
+      &fake_system_state_,
+      OmahaRequestParams::kOsPlatform,
+      OmahaRequestParams::kOsVersion,
+      "service_pack",
+      "x86-generic",
+      OmahaRequestParams::kAppId,
+      "0.1.0.0",
+      "en-US",
+      "unittest",
+      "OEM MODEL 09235 7471",
+      "ChromeOSFirmware.1.0",
+      "0X0A1",
+      false,   // delta okay
+      false,   // interactive
+      "http://url",
+      ""};     // target_version_prefix
+
+  FakePrefs fake_prefs_;
+};
+
+namespace {
+class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  OmahaRequestActionTestProcessorDelegate()
+      : expected_code_(ErrorCode::kSuccess) {}
+  ~OmahaRequestActionTestProcessorDelegate() override {
+  }
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) override {
+    chromeos::MessageLoop::current()->BreakLoop();
+  }
+
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) override {
+    // make sure actions always succeed
+    if (action->Type() == OmahaRequestAction::StaticType())
+      EXPECT_EQ(expected_code_, code);
+    else
+      EXPECT_EQ(ErrorCode::kSuccess, code);
+  }
+  ErrorCode expected_code_;
+};
+}  // namespace
+
+class OutputObjectCollectorAction;
+
+template<>
+class ActionTraits<OutputObjectCollectorAction> {
+ public:
+  // Does not take an object for input
+  typedef OmahaResponse InputObjectType;
+  // On success, puts the output path on output
+  typedef NoneType OutputObjectType;
+};
+
+class OutputObjectCollectorAction : public Action<OutputObjectCollectorAction> {
+ public:
+  OutputObjectCollectorAction() : has_input_object_(false) {}
+  void PerformAction() {
+    // copy input object
+    has_input_object_ = HasInputObject();
+    if (has_input_object_)
+      omaha_response_ = GetInputObject();
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  // Should never be called
+  void TerminateProcessing() {
+    CHECK(false);
+  }
+  // Debugging/logging
+  static string StaticType() {
+    return "OutputObjectCollectorAction";
+  }
+  string Type() const { return StaticType(); }
+  bool has_input_object_;
+  OmahaResponse omaha_response_;
+};
+
+bool OmahaRequestActionTest::TestUpdateCheck(
+    OmahaRequestParams* request_params,
+    const string& http_response,
+    int fail_http_response_code,
+    bool ping_only,
+    ErrorCode expected_code,
+    metrics::CheckResult expected_check_result,
+    metrics::CheckReaction expected_check_reaction,
+    metrics::DownloadErrorCode expected_download_error_code,
+    OmahaResponse* out_response,
+    chromeos::Blob* out_post_data) {
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+  MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+                                                 http_response.size(),
+                                                 nullptr);
+  if (fail_http_response_code >= 0) {
+    fetcher->FailTransfer(fail_http_response_code);
+  }
+  if (request_params)
+    fake_system_state_.set_request_params(request_params);
+  OmahaRequestAction action(&fake_system_state_,
+                            nullptr,
+                            fetcher,
+                            ping_only);
+  OmahaRequestActionTestProcessorDelegate delegate;
+  delegate.expected_code_ = expected_code;
+
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  OutputObjectCollectorAction collector_action;
+  BondActions(&action, &collector_action);
+  processor.EnqueueAction(&collector_action);
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+      .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+      SendEnumToUMA(metrics::kMetricCheckResult,
+          static_cast<int>(expected_check_result),
+          static_cast<int>(metrics::CheckResult::kNumConstants) - 1))
+      .Times(expected_check_result == metrics::CheckResult::kUnset ? 0 : 1);
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+      SendEnumToUMA(metrics::kMetricCheckReaction,
+          static_cast<int>(expected_check_reaction),
+          static_cast<int>(metrics::CheckReaction::kNumConstants) - 1))
+      .Times(expected_check_reaction == metrics::CheckReaction::kUnset ? 0 : 1);
+  EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+      SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode,
+          static_cast<int>(expected_download_error_code)))
+      .Times(expected_download_error_code == metrics::DownloadErrorCode::kUnset
+             ? 0 : 1);
+
+  loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+  LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
+  loop.Run();
+  LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
+  EXPECT_FALSE(loop.PendingTasks());
+  if (collector_action.has_input_object_ && out_response)
+    *out_response = collector_action.omaha_response_;
+  if (out_post_data)
+    *out_post_data = fetcher->post_data();
+  return collector_action.has_input_object_;
+}
+
+// Tests Event requests -- they should always succeed. |out_post_data|
+// may be null; if non-null, the post-data received by the mock
+// HttpFetcher is returned.
+void TestEvent(OmahaRequestParams params,
+               OmahaEvent* event,
+               const string& http_response,
+               chromeos::Blob* out_post_data) {
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+  MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+                                                 http_response.size(),
+                                                 nullptr);
+  FakeSystemState fake_system_state;
+  fake_system_state.set_request_params(&params);
+  OmahaRequestAction action(&fake_system_state, event, fetcher, false);
+  OmahaRequestActionTestProcessorDelegate delegate;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+  loop.Run();
+
+  // This test should schedule a callback to notify the crash reporter if
+  // the passed event is an error.
+  EXPECT_EQ(event->result == OmahaEvent::kResultError, loop.PendingTasks());
+
+  if (out_post_data)
+    *out_post_data = fetcher->post_data();
+}
+
+TEST_F(OmahaRequestActionTest, RejectEntities) {
+  OmahaResponse response;
+  fake_update_response_.include_entity = true;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestXMLHasEntityDecl,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoUpdateTest) {
+  OmahaResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+// Test that all the values in the response are parsed in a normal update
+// response.
+TEST_F(OmahaRequestActionTest, ValidUpdateTest) {
+  OmahaResponse response;
+  fake_update_response_.deadline = "20101020";
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.version, response.version);
+  EXPECT_EQ(fake_update_response_.GetPayloadUrl(), response.payload_urls[0]);
+  EXPECT_EQ(fake_update_response_.more_info_url, response.more_info_url);
+  EXPECT_EQ(fake_update_response_.hash, response.hash);
+  EXPECT_EQ(fake_update_response_.size, response.size);
+  EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt);
+  EXPECT_EQ(fake_update_response_.deadline, response.deadline);
+  // Omaha cohort attribets are not set in the response, so they should not be
+  // persisted.
+  EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohort));
+  EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortHint));
+  EXPECT_FALSE(fake_prefs_.Exists(kPrefsOmahaCohortName));
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) {
+  OmahaResponse response;
+  // Set up a connection manager that doesn't allow a valid update over
+  // the current ethernet connection.
+  MockConnectionManager mock_cm(nullptr);
+  fake_system_state_.set_connection_manager(&mock_cm);
+
+  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _, _))
+    .WillRepeatedly(DoAll(SetArgumentPointee<1>(kNetEthernet),
+                          SetArgumentPointee<2>(NetworkTethering::kUnknown),
+                          Return(true)));
+  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(kNetEthernet, _))
+    .WillRepeatedly(Return(false));
+  EXPECT_CALL(mock_cm, StringForConnectionType(kNetEthernet))
+    .WillRepeatedly(Return(shill::kTypeEthernet));
+
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kIgnored,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) {
+  string rollback_version = "1234.0.0";
+  OmahaResponse response;
+
+  MockPayloadState mock_payload_state;
+  fake_system_state_.set_payload_state(&mock_payload_state);
+
+  EXPECT_CALL(mock_payload_state, GetRollbackVersion())
+    .WillRepeatedly(Return(rollback_version));
+
+  fake_update_response_.version = rollback_version;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kIgnored,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_update_check_count_wait_enabled(false);
+  params.set_waiting_period(TimeDelta::FromDays(2));
+
+  ASSERT_FALSE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kDeferring,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NoWallClockBasedWaitCausesNoScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(false);
+  params.set_waiting_period(TimeDelta::FromDays(2));
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ZeroMaxDaysToScatterCausesNoScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta::FromDays(2));
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  fake_update_response_.max_days_to_scatter = "0";
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+
+TEST_F(OmahaRequestActionTest, ZeroUpdateCheckCountCausesNoScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta());
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(0);
+  params.set_max_update_checks_allowed(0);
+
+  ASSERT_TRUE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  int64_t count;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+  ASSERT_EQ(count, 0);
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NonZeroUpdateCheckCountCausesScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta());
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  ASSERT_FALSE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kDeferring,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  int64_t count;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+  ASSERT_GT(count, 0);
+  EXPECT_FALSE(response.update_exists);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ExistingUpdateCheckCountCausesScattering) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta());
+
+  params.set_update_check_count_wait_enabled(true);
+  params.set_min_update_checks_needed(1);
+  params.set_max_update_checks_allowed(8);
+
+  ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
+
+  ASSERT_FALSE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kDeferring,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  int64_t count;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateCheckCount, &count));
+  // count remains the same, as the decrementing happens in update_attempter
+  // which this test doesn't exercise.
+  ASSERT_EQ(count, 5);
+  EXPECT_FALSE(response.update_exists);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, CohortsArePersisted) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  fake_update_response_.include_cohorts = true;
+  fake_update_response_.cohort = "s/154454/8479665";
+  fake_update_response_.cohorthint = "please-put-me-on-beta";
+  fake_update_response_.cohortname = "stable";
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  string value;
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+  EXPECT_EQ(fake_update_response_.cohort, value);
+
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+  EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+  EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
+TEST_F(OmahaRequestActionTest, CohortsAreUpdated) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+  EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortHint, "old_hint"));
+  EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortName, "old_name"));
+  fake_update_response_.include_cohorts = true;
+  fake_update_response_.cohort = "s/154454/8479665";
+  fake_update_response_.cohorthint = "please-put-me-on-beta";
+  fake_update_response_.cohortname = "";
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  string value;
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+  EXPECT_EQ(fake_update_response_.cohort, value);
+
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+  EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+  EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+}
+
+TEST_F(OmahaRequestActionTest, CohortsAreNotModifiedWhenMissing) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  string value;
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+  EXPECT_EQ("old_value", value);
+
+  EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+  EXPECT_FALSE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+}
+
+TEST_F(OmahaRequestActionTest, CohortsArePersistedWhenNoUpdate) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  fake_update_response_.include_cohorts = true;
+  fake_update_response_.cohort = "s/154454/8479665";
+  fake_update_response_.cohorthint = "please-put-me-on-beta";
+  fake_update_response_.cohortname = "stable";
+
+  ASSERT_TRUE(TestUpdateCheck(&params,
+                              fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+
+  string value;
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohort, &value));
+  EXPECT_EQ(fake_update_response_.cohort, value);
+
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortHint, &value));
+  EXPECT_EQ(fake_update_response_.cohorthint, value);
+
+  EXPECT_TRUE(fake_prefs_.GetString(kPrefsOmahaCohortName, &value));
+  EXPECT_EQ(fake_update_response_.cohortname, value);
+}
+
+TEST_F(OmahaRequestActionTest, NoOutputPipeTest) {
+  const string http_response(fake_update_response_.GetNoUpdateResponse());
+
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  OmahaRequestParams params = request_params_;
+  fake_system_state_.set_request_params(&params);
+  OmahaRequestAction action(&fake_system_state_, nullptr,
+                            new MockHttpFetcher(http_response.data(),
+                                                http_response.size(),
+                                                nullptr),
+                            false);
+  OmahaRequestActionTestProcessorDelegate delegate;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+  EXPECT_FALSE(processor.IsRunning());
+}
+
+TEST_F(OmahaRequestActionTest, InvalidXmlTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "invalid xml>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestXMLParseError,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, EmptyResponseTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestEmptyResponseError,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingStatusTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(TestUpdateCheck(
+      nullptr,  // request_params
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+      "<daystart elapsed_seconds=\"100\"/>"
+      "<app appid=\"foo\" status=\"ok\">"
+      "<ping status=\"ok\"/>"
+      "<updatecheck/></app></response>",
+      -1,
+      false,  // ping_only
+      ErrorCode::kOmahaResponseInvalid,
+      metrics::CheckResult::kParsingError,
+      metrics::CheckReaction::kUnset,
+      metrics::DownloadErrorCode::kUnset,
+      &response,
+      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, InvalidStatusTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(TestUpdateCheck(
+      nullptr,  // request_params
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+      "<daystart elapsed_seconds=\"100\"/>"
+      "<app appid=\"foo\" status=\"ok\">"
+      "<ping status=\"ok\"/>"
+      "<updatecheck status=\"InvalidStatusTest\"/></app></response>",
+      -1,
+      false,  // ping_only
+      ErrorCode::kOmahaResponseInvalid,
+      metrics::CheckResult::kParsingError,
+      metrics::CheckReaction::kUnset,
+      metrics::DownloadErrorCode::kUnset,
+      &response,
+      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingNodesetTest) {
+  OmahaResponse response;
+  ASSERT_FALSE(TestUpdateCheck(
+      nullptr,  // request_params
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+      "<daystart elapsed_seconds=\"100\"/>"
+      "<app appid=\"foo\" status=\"ok\">"
+      "<ping status=\"ok\"/>"
+      "</app></response>",
+      -1,
+      false,  // ping_only
+      ErrorCode::kOmahaResponseInvalid,
+      metrics::CheckResult::kParsingError,
+      metrics::CheckReaction::kUnset,
+      metrics::DownloadErrorCode::kUnset,
+      &response,
+      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, MissingFieldTest) {
+  string input_response =
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
+      "<daystart elapsed_seconds=\"100\"/>"
+      "<app appid=\"xyz\" status=\"ok\">"
+      "<updatecheck status=\"ok\">"
+      "<urls><url codebase=\"http://missing/field/test/\"/></urls>"
+      "<manifest version=\"10.2.3.4\">"
+      "<packages><package hash=\"not-used\" name=\"f\" "
+      "size=\"587\"/></packages>"
+      "<actions><action event=\"postinstall\" "
+      "ChromeOSVersion=\"10.2.3.4\" "
+      "Prompt=\"false\" "
+      "IsDelta=\"true\" "
+      "IsDeltaPayload=\"false\" "
+      "sha256=\"lkq34j5345\" "
+      "needsadmin=\"true\" "
+      "/></actions></manifest></updatecheck></app></response>";
+  LOG(INFO) << "Input Response = " << input_response;
+
+  OmahaResponse response;
+  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
+                              input_response,
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ("10.2.3.4", response.version);
+  EXPECT_EQ("http://missing/field/test/f", response.payload_urls[0]);
+  EXPECT_EQ("", response.more_info_url);
+  EXPECT_EQ("lkq34j5345", response.hash);
+  EXPECT_EQ(587, response.size);
+  EXPECT_FALSE(response.prompt);
+  EXPECT_TRUE(response.deadline.empty());
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  void ProcessingStopped(const ActionProcessor* processor) {
+    chromeos::MessageLoop::current()->BreakLoop();
+  }
+};
+
+void TerminateTransferTestStarter(ActionProcessor* processor) {
+  processor->StartProcessing();
+  CHECK(processor->IsRunning());
+  processor->StopProcessing();
+}
+}  // namespace
+
+TEST_F(OmahaRequestActionTest, TerminateTransferTest) {
+  chromeos::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  string http_response("doesn't matter");
+  OmahaRequestAction action(&fake_system_state_, nullptr,
+                            new MockHttpFetcher(http_response.data(),
+                                                http_response.size(),
+                                                nullptr),
+                            false);
+  TerminateEarlyTestProcessorDelegate delegate;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  loop.PostTask(base::Bind(&TerminateTransferTestStarter, &processor));
+  loop.Run();
+  EXPECT_FALSE(loop.PendingTasks());
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeTest) {
+  string output;
+  EXPECT_TRUE(XmlEncode("ab", &output));
+  EXPECT_EQ("ab", output);
+  EXPECT_TRUE(XmlEncode("a<b", &output));
+  EXPECT_EQ("a&lt;b", output);
+  EXPECT_TRUE(XmlEncode("<&>\"\'\\", &output));
+  EXPECT_EQ("&lt;&amp;&gt;&quot;&apos;\\", output);
+  EXPECT_TRUE(XmlEncode("&lt;&amp;&gt;", &output));
+  EXPECT_EQ("&amp;lt;&amp;amp;&amp;gt;", output);
+  // Check that unterminated UTF-8 strings are handled properly.
+  EXPECT_FALSE(XmlEncode("\xc2", &output));
+  // Fail with invalid ASCII-7 chars.
+  EXPECT_FALSE(XmlEncode("This is an 'n' with a tilde: \xc3\xb1", &output));
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeWithDefaultTest) {
+  EXPECT_EQ("&lt;&amp;&gt;", XmlEncodeWithDefault("<&>", "something else"));
+  EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>"));
+}
+
+TEST_F(OmahaRequestActionTest, XmlEncodeIsUsedForParams) {
+  chromeos::Blob post_data;
+
+  // Make sure XML Encode is being called on the params
+  OmahaRequestParams params(&fake_system_state_,
+                            OmahaRequestParams::kOsPlatform,
+                            OmahaRequestParams::kOsVersion,
+                            "testtheservice_pack>",
+                            "x86 generic<id",
+                            OmahaRequestParams::kAppId,
+                            "0.1.0.0",
+                            "en-US",
+                            "unittest_track&lt;",
+                            "<OEM MODEL>",
+                            "ChromeOSFirmware.1.0",
+                            "EC100",
+                            false,   // delta okay
+                            false,   // interactive
+                            "http://url",
+                            "");     // target_version_prefix
+  fake_prefs_.SetString(kPrefsOmahaCohort, "evil\nstring");
+  fake_prefs_.SetString(kPrefsOmahaCohortHint, "evil&string\\");
+  fake_prefs_.SetString(kPrefsOmahaCohortName,
+                        JoinString(vector<string>(100, "My spoon is too big."),
+                                   ' '));
+  OmahaResponse response;
+  ASSERT_FALSE(
+      TestUpdateCheck(&params,
+                      "invalid xml>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaRequestXMLParseError,
+                      metrics::CheckResult::kParsingError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(string::npos, post_str.find("testtheservice_pack&gt;"));
+  EXPECT_EQ(string::npos, post_str.find("testtheservice_pack>"));
+  EXPECT_NE(string::npos, post_str.find("x86 generic&lt;id"));
+  EXPECT_EQ(string::npos, post_str.find("x86 generic<id"));
+  EXPECT_NE(string::npos, post_str.find("unittest_track&amp;lt;"));
+  EXPECT_EQ(string::npos, post_str.find("unittest_track&lt;"));
+  EXPECT_NE(string::npos, post_str.find("&lt;OEM MODEL&gt;"));
+  EXPECT_EQ(string::npos, post_str.find("<OEM MODEL>"));
+  EXPECT_NE(string::npos, post_str.find("cohort=\"evil\nstring\""));
+  EXPECT_EQ(string::npos, post_str.find("cohorthint=\"evil&string\\\""));
+  EXPECT_NE(string::npos, post_str.find("cohorthint=\"evil&amp;string\\\""));
+  // Values from Prefs that are too big are removed from the XML instead of
+  // encoded.
+  EXPECT_EQ(string::npos, post_str.find("cohortname="));
+}
+
+TEST_F(OmahaRequestActionTest, XmlDecodeTest) {
+  OmahaResponse response;
+  fake_update_response_.deadline = "&lt;20110101";
+  fake_update_response_.more_info_url = "testthe&lt;url";
+  fake_update_response_.codebase = "testthe&amp;codebase/";
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  EXPECT_EQ(response.more_info_url, "testthe<url");
+  EXPECT_EQ(response.payload_urls[0], "testthe&codebase/file.signed");
+  EXPECT_EQ(response.deadline, "<20110101");
+}
+
+TEST_F(OmahaRequestActionTest, ParseIntTest) {
+  OmahaResponse response;
+  // overflows int32_t:
+  fake_update_response_.size = 123123123123123ll;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  EXPECT_EQ(response.size, 123123123123123ll);
+}
+
+TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) {
+  chromeos::Blob post_data;
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+
+  EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(string("")), Return(true)));
+  EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, _)).Times(1);
+  ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(post_str.find(
+      "        <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n"
+      "        <updatecheck targetversionprefix=\"\"></updatecheck>\n"),
+      string::npos);
+  EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
+            string::npos);
+  EXPECT_NE(post_str.find("fw_version=\"ChromeOSFirmware.1.0\""),
+            string::npos);
+  EXPECT_NE(post_str.find("ec_version=\"0X0A1\""),
+            string::npos);
+}
+
+
+TEST_F(OmahaRequestActionTest, FormatSuccessEventOutputTest) {
+  chromeos::Blob post_data;
+  TestEvent(request_params_,
+            new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
+            "invalid xml>",
+            &post_data);
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  string expected_event = base::StringPrintf(
+      "        <event eventtype=\"%d\" eventresult=\"%d\"></event>\n",
+      OmahaEvent::kTypeUpdateDownloadStarted,
+      OmahaEvent::kResultSuccess);
+  EXPECT_NE(post_str.find(expected_event), string::npos);
+  EXPECT_EQ(post_str.find("ping"), string::npos);
+  EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, FormatErrorEventOutputTest) {
+  chromeos::Blob post_data;
+  TestEvent(request_params_,
+            new OmahaEvent(OmahaEvent::kTypeDownloadComplete,
+                           OmahaEvent::kResultError,
+                           ErrorCode::kError),
+            "invalid xml>",
+            &post_data);
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  string expected_event = base::StringPrintf(
+      "        <event eventtype=\"%d\" eventresult=\"%d\" "
+      "errorcode=\"%d\"></event>\n",
+      OmahaEvent::kTypeDownloadComplete,
+      OmahaEvent::kResultError,
+      static_cast<int>(ErrorCode::kError));
+  EXPECT_NE(post_str.find(expected_event), string::npos);
+  EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, IsEventTest) {
+  string http_response("doesn't matter");
+  // Create a copy of the OmahaRequestParams to reuse it later.
+  OmahaRequestParams params = request_params_;
+  fake_system_state_.set_request_params(&params);
+  OmahaRequestAction update_check_action(
+      &fake_system_state_,
+      nullptr,
+      new MockHttpFetcher(http_response.data(),
+                          http_response.size(),
+                          nullptr),
+      false);
+  EXPECT_FALSE(update_check_action.IsEvent());
+
+  params = request_params_;
+  fake_system_state_.set_request_params(&params);
+  OmahaRequestAction event_action(
+      &fake_system_state_,
+      new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+      new MockHttpFetcher(http_response.data(),
+                          http_response.size(),
+                          nullptr),
+      false);
+  EXPECT_TRUE(event_action.IsEvent());
+}
+
+TEST_F(OmahaRequestActionTest, FormatDeltaOkayOutputTest) {
+  for (int i = 0; i < 2; i++) {
+    bool delta_okay = i == 1;
+    const char* delta_okay_str = delta_okay ? "true" : "false";
+    chromeos::Blob post_data;
+    OmahaRequestParams params(&fake_system_state_,
+                              OmahaRequestParams::kOsPlatform,
+                              OmahaRequestParams::kOsVersion,
+                              "service_pack",
+                              "x86-generic",
+                              OmahaRequestParams::kAppId,
+                              "0.1.0.0",
+                              "en-US",
+                              "unittest_track",
+                              "OEM MODEL REV 1234",
+                              "ChromeOSFirmware.1.0",
+                              "EC100",
+                              delta_okay,
+                              false,  // interactive
+                              "http://url",
+                              "");    // target_version_prefix
+    ASSERT_FALSE(TestUpdateCheck(&params,
+                                 "invalid xml>",
+                                 -1,
+                                 false,  // ping_only
+                                 ErrorCode::kOmahaRequestXMLParseError,
+                                 metrics::CheckResult::kParsingError,
+                                 metrics::CheckReaction::kUnset,
+                                 metrics::DownloadErrorCode::kUnset,
+                                 nullptr,
+                                 &post_data));
+    // convert post_data to string
+    string post_str(post_data.begin(), post_data.end());
+    EXPECT_NE(post_str.find(base::StringPrintf(" delta_okay=\"%s\"",
+                                               delta_okay_str)),
+              string::npos)
+        << "i = " << i;
+  }
+}
+
+TEST_F(OmahaRequestActionTest, FormatInteractiveOutputTest) {
+  for (int i = 0; i < 2; i++) {
+    bool interactive = i == 1;
+    const char* interactive_str = interactive ? "ondemandupdate" : "scheduler";
+    chromeos::Blob post_data;
+    FakeSystemState fake_system_state;
+    OmahaRequestParams params(&fake_system_state_,
+                              OmahaRequestParams::kOsPlatform,
+                              OmahaRequestParams::kOsVersion,
+                              "service_pack",
+                              "x86-generic",
+                              OmahaRequestParams::kAppId,
+                              "0.1.0.0",
+                              "en-US",
+                              "unittest_track",
+                              "OEM MODEL REV 1234",
+                              "ChromeOSFirmware.1.0",
+                              "EC100",
+                              true,   // delta_okay
+                              interactive,
+                              "http://url",
+                              "");    // target_version_prefix
+    ASSERT_FALSE(TestUpdateCheck(&params,
+                                 "invalid xml>",
+                                 -1,
+                                 false,  // ping_only
+                                 ErrorCode::kOmahaRequestXMLParseError,
+                                 metrics::CheckResult::kParsingError,
+                                 metrics::CheckReaction::kUnset,
+                                 metrics::DownloadErrorCode::kUnset,
+                                 nullptr,
+                                 &post_data));
+    // convert post_data to string
+    string post_str(post_data.begin(), post_data.end());
+    EXPECT_NE(post_str.find(base::StringPrintf("installsource=\"%s\"",
+                                               interactive_str)),
+              string::npos)
+        << "i = " << i;
+  }
+}
+
+TEST_F(OmahaRequestActionTest, OmahaEventTest) {
+  OmahaEvent default_event;
+  EXPECT_EQ(OmahaEvent::kTypeUnknown, default_event.type);
+  EXPECT_EQ(OmahaEvent::kResultError, default_event.result);
+  EXPECT_EQ(ErrorCode::kError, default_event.error_code);
+
+  OmahaEvent success_event(OmahaEvent::kTypeUpdateDownloadStarted);
+  EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadStarted, success_event.type);
+  EXPECT_EQ(OmahaEvent::kResultSuccess, success_event.result);
+  EXPECT_EQ(ErrorCode::kSuccess, success_event.error_code);
+
+  OmahaEvent error_event(OmahaEvent::kTypeUpdateDownloadFinished,
+                         OmahaEvent::kResultError,
+                         ErrorCode::kError);
+  EXPECT_EQ(OmahaEvent::kTypeUpdateDownloadFinished, error_event.type);
+  EXPECT_EQ(OmahaEvent::kResultError, error_event.result);
+  EXPECT_EQ(ErrorCode::kError, error_event.error_code);
+}
+
+void OmahaRequestActionTest::PingTest(bool ping_only) {
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  // Add a few hours to the day difference to test no rounding, etc.
+  int64_t five_days_ago =
+      (Time::Now() - TimeDelta::FromHours(5 * 24 + 13)).ToInternalValue();
+  int64_t six_days_ago =
+      (Time::Now() - TimeDelta::FromHours(6 * 24 + 11)).ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(six_days_ago), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(five_days_ago), Return(true)));
+  chromeos::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      ping_only,
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUnset,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(post_str.find("<ping active=\"1\" a=\"6\" r=\"5\"></ping>"),
+            string::npos);
+  if (ping_only) {
+    EXPECT_EQ(post_str.find("updatecheck"), string::npos);
+    EXPECT_EQ(post_str.find("previousversion"), string::npos);
+  } else {
+    EXPECT_NE(post_str.find("updatecheck"), string::npos);
+    EXPECT_NE(post_str.find("previousversion"), string::npos);
+  }
+}
+
+TEST_F(OmahaRequestActionTest, PingTestSendOnlyAPing) {
+  PingTest(true  /* ping_only */);
+}
+
+TEST_F(OmahaRequestActionTest, PingTestSendAlsoAnUpdateCheck) {
+  PingTest(false  /* ping_only */);
+}
+
+TEST_F(OmahaRequestActionTest, ActivePingTest) {
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  int64_t three_days_ago =
+      (Time::Now() - TimeDelta::FromHours(3 * 24 + 12)).ToInternalValue();
+  int64_t now = Time::Now().ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(three_days_ago), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+  chromeos::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(post_str.find("<ping active=\"1\" a=\"3\"></ping>"),
+            string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, RollCallPingTest) {
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  int64_t four_days_ago =
+      (Time::Now() - TimeDelta::FromHours(4 * 24)).ToInternalValue();
+  int64_t now = Time::Now().ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(four_days_ago), Return(true)));
+  chromeos::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(post_str.find("<ping active=\"1\" r=\"4\"></ping>\n"),
+            string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, NoPingTest) {
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  int64_t one_hour_ago =
+      (Time::Now() - TimeDelta::FromHours(1)).ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(one_hour_ago), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(one_hour_ago), Return(true)));
+  // LastActivePingDay and PrefsLastRollCallPingDay are set even if we didn't
+  // send a ping.
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(Return(true));
+  chromeos::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_EQ(post_str.find("ping"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, IgnoreEmptyPingTest) {
+  // This test ensures that we ignore empty ping only requests.
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  int64_t now = Time::Now().ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+  chromeos::Blob post_data;
+  EXPECT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      true,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUnset,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  EXPECT_EQ(post_data.size(), 0);
+}
+
+TEST_F(OmahaRequestActionTest, BackInTimePingTest) {
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  int64_t future =
+      (Time::Now() + TimeDelta::FromHours(3 * 24 + 4)).ToInternalValue();
+  EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(0), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
+  EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
+      .WillOnce(Return(true));
+  chromeos::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+                      "protocol=\"3.0\"><daystart elapsed_seconds=\"100\"/>"
+                      "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+                      "<updatecheck status=\"noupdate\"/></app></response>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_EQ(post_str.find("ping"), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, LastPingDayUpdateTest) {
+  // This test checks that the action updates the last ping day to now
+  // minus 200 seconds with a slack of 5 seconds. Therefore, the test
+  // may fail if it runs for longer than 5 seconds. It shouldn't run
+  // that long though.
+  int64_t midnight =
+      (Time::Now() - TimeDelta::FromSeconds(200)).ToInternalValue();
+  int64_t midnight_slack =
+      (Time::Now() - TimeDelta::FromSeconds(195)).ToInternalValue();
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay,
+                              AllOf(Ge(midnight), Le(midnight_slack))))
+      .WillOnce(Return(true));
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay,
+                              AllOf(Ge(midnight), Le(midnight_slack))))
+      .WillOnce(Return(true));
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+                      "protocol=\"3.0\"><daystart elapsed_seconds=\"200\"/>"
+                      "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+                      "<updatecheck status=\"noupdate\"/></app></response>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+}
+
+TEST_F(OmahaRequestActionTest, NoElapsedSecondsTest) {
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+                      "protocol=\"3.0\"><daystart blah=\"200\"/>"
+                      "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+                      "<updatecheck status=\"noupdate\"/></app></response>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+}
+
+TEST_F(OmahaRequestActionTest, BadElapsedSecondsTest) {
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  EXPECT_CALL(prefs, GetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
+  EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+                      "protocol=\"3.0\"><daystart elapsed_seconds=\"x\"/>"
+                      "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
+                      "<updatecheck status=\"noupdate\"/></app></response>",
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      nullptr));
+}
+
+TEST_F(OmahaRequestActionTest, NoUniqueIDTest) {
+  chromeos::Blob post_data;
+  ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_EQ(post_str.find("machineid="), string::npos);
+  EXPECT_EQ(post_str.find("userid="), string::npos);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureTest) {
+  OmahaResponse response;
+  const int http_error_code =
+      static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 501;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "",
+                      501,
+                      false,  // ping_only
+                      static_cast<ErrorCode>(http_error_code),
+                      metrics::CheckResult::kDownloadError,
+                      metrics::CheckReaction::kUnset,
+                      static_cast<metrics::DownloadErrorCode>(501),
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, NetworkFailureBadHTTPCodeTest) {
+  OmahaResponse response;
+  const int http_error_code =
+      static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 999;
+  ASSERT_FALSE(
+      TestUpdateCheck(nullptr,  // request_params
+                      "",
+                      1500,
+                      false,  // ping_only
+                      static_cast<ErrorCode>(http_error_code),
+                      metrics::CheckResult::kDownloadError,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kHttpStatusOther,
+                      &response,
+                      nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsPersistedFirstTime) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta().FromDays(1));
+  params.set_update_check_count_wait_enabled(false);
+
+  ASSERT_FALSE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kDeferring,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  int64_t timestamp = 0;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, &timestamp));
+  ASSERT_GT(timestamp, 0);
+  EXPECT_FALSE(response.update_exists);
+
+  // Verify if we are interactive check we don't defer.
+  params.set_interactive(true);
+  ASSERT_TRUE(
+      TestUpdateCheck(&params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsUsedIfAlreadyPresent) {
+  OmahaResponse response;
+  OmahaRequestParams params = request_params_;
+  params.set_wall_clock_based_wait_enabled(true);
+  params.set_waiting_period(TimeDelta().FromDays(1));
+  params.set_update_check_count_wait_enabled(false);
+
+  // Set the timestamp to a very old value such that it exceeds the
+  // waiting period set above.
+  Time t1;
+  Time::FromString("1/1/2012", &t1);
+  ASSERT_TRUE(fake_prefs_.SetInt64(
+      kPrefsUpdateFirstSeenAt, t1.ToInternalValue()));
+  ASSERT_TRUE(TestUpdateCheck(
+                      &params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+
+  EXPECT_TRUE(response.update_exists);
+
+  // Make sure the timestamp t1 is unchanged showing that it was reused.
+  int64_t timestamp = 0;
+  ASSERT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt, &timestamp));
+  ASSERT_TRUE(timestamp == t1.ToInternalValue());
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToMoreStableChannel) {
+  // Create a uniquely named test directory.
+  string test_dir;
+  ASSERT_TRUE(utils::MakeTempDirectory(
+          "omaha_request_action-test-XXXXXX", &test_dir));
+
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+                      kStatefulPartition + "/etc"));
+  chromeos::Blob post_data;
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  ASSERT_TRUE(WriteFileString(
+      test_dir + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_APPID={11111111-1111-1111-1111-111111111111}\n"
+      "CHROMEOS_BOARD_APPID={22222222-2222-2222-2222-222222222222}\n"
+      "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  OmahaRequestParams params = request_params_;
+  params.set_root(test_dir);
+  params.SetLockDown(false);
+  params.Init("1.2.3.4", "", 0);
+  EXPECT_EQ("canary-channel", params.current_channel());
+  EXPECT_EQ("stable-channel", params.target_channel());
+  EXPECT_TRUE(params.to_more_stable_channel());
+  EXPECT_TRUE(params.is_powerwash_allowed());
+  ASSERT_FALSE(TestUpdateCheck(&params,
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(string::npos, post_str.find(
+      "appid=\"{22222222-2222-2222-2222-222222222222}\" "
+      "version=\"0.0.0.0\" from_version=\"1.2.3.4\" "
+      "track=\"stable-channel\" from_track=\"canary-channel\" "));
+
+  ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) {
+  // Create a uniquely named test directory.
+  string test_dir;
+  ASSERT_TRUE(utils::MakeTempDirectory(
+          "omaha_request_action-test-XXXXXX", &test_dir));
+
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+                      kStatefulPartition + "/etc"));
+  chromeos::Blob post_data;
+  NiceMock<MockPrefs> prefs;
+  fake_system_state_.set_prefs(&prefs);
+  ASSERT_TRUE(WriteFileString(
+      test_dir + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_APPID={11111111-1111-1111-1111-111111111111}\n"
+      "CHROMEOS_BOARD_APPID={22222222-2222-2222-2222-222222222222}\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+  OmahaRequestParams params = request_params_;
+  params.set_root(test_dir);
+  params.SetLockDown(false);
+  params.Init("5.6.7.8", "", 0);
+  EXPECT_EQ("stable-channel", params.current_channel());
+  EXPECT_EQ("canary-channel", params.target_channel());
+  EXPECT_FALSE(params.to_more_stable_channel());
+  EXPECT_FALSE(params.is_powerwash_allowed());
+  ASSERT_FALSE(TestUpdateCheck(&params,
+                               "invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               nullptr,  // response
+                               &post_data));
+  // convert post_data to string
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_NE(string::npos, post_str.find(
+      "appid=\"{11111111-1111-1111-1111-111111111111}\" "
+      "version=\"5.6.7.8\" "
+      "track=\"canary-channel\" from_track=\"stable-channel\""));
+  EXPECT_EQ(string::npos, post_str.find("from_version"));
+}
+
+// Checks that the initial ping with a=-1 r=-1 is not send when the device
+// was powerwashed.
+TEST_F(OmahaRequestActionTest, PingWhenPowerwashed) {
+  fake_prefs_.SetString(kPrefsPreviousVersion, "");
+
+  // Flag that the device was powerwashed in the past.
+  fake_system_state_.fake_hardware()->SetPowerwashCount(1);
+
+  chromeos::Blob post_data;
+  ASSERT_TRUE(
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetNoUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kNoUpdateAvailable,
+                      metrics::CheckReaction::kUnset,
+                      metrics::DownloadErrorCode::kUnset,
+                      nullptr,
+                      &post_data));
+  // We shouldn't send a ping in this case since powerwash > 0.
+  string post_str(post_data.begin(), post_data.end());
+  EXPECT_EQ(string::npos, post_str.find("<ping"));
+}
+
+void OmahaRequestActionTest::P2PTest(
+    bool initial_allow_p2p_for_downloading,
+    bool initial_allow_p2p_for_sharing,
+    bool omaha_disable_p2p_for_downloading,
+    bool omaha_disable_p2p_for_sharing,
+    bool payload_state_allow_p2p_attempt,
+    bool expect_p2p_client_lookup,
+    const string& p2p_client_result_url,
+    bool expected_allow_p2p_for_downloading,
+    bool expected_allow_p2p_for_sharing,
+    const string& expected_p2p_url) {
+  OmahaResponse response;
+  OmahaRequestParams request_params = request_params_;
+  bool actual_allow_p2p_for_downloading = initial_allow_p2p_for_downloading;
+  bool actual_allow_p2p_for_sharing = initial_allow_p2p_for_sharing;
+  string actual_p2p_url;
+
+  MockPayloadState mock_payload_state;
+  fake_system_state_.set_payload_state(&mock_payload_state);
+  EXPECT_CALL(mock_payload_state, P2PAttemptAllowed())
+      .WillRepeatedly(Return(payload_state_allow_p2p_attempt));
+  EXPECT_CALL(mock_payload_state, GetUsingP2PForDownloading())
+      .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_downloading));
+  EXPECT_CALL(mock_payload_state, GetUsingP2PForSharing())
+      .WillRepeatedly(ReturnPointee(&actual_allow_p2p_for_sharing));
+  EXPECT_CALL(mock_payload_state, SetUsingP2PForDownloading(_))
+      .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_downloading));
+  EXPECT_CALL(mock_payload_state, SetUsingP2PForSharing(_))
+      .WillRepeatedly(SaveArg<0>(&actual_allow_p2p_for_sharing));
+  EXPECT_CALL(mock_payload_state, SetP2PUrl(_))
+      .WillRepeatedly(SaveArg<0>(&actual_p2p_url));
+
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  mock_p2p_manager.fake().SetLookupUrlForFileResult(p2p_client_result_url);
+
+  TimeDelta timeout = TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds);
+  EXPECT_CALL(mock_p2p_manager, LookupUrlForFile(_, _, timeout, _))
+      .Times(expect_p2p_client_lookup ? 1 : 0);
+
+  fake_update_response_.disable_p2p_for_downloading =
+      omaha_disable_p2p_for_downloading;
+  fake_update_response_.disable_p2p_for_sharing = omaha_disable_p2p_for_sharing;
+  ASSERT_TRUE(
+      TestUpdateCheck(&request_params,
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      &response,
+                      nullptr));
+  EXPECT_TRUE(response.update_exists);
+
+  EXPECT_EQ(omaha_disable_p2p_for_downloading,
+            response.disable_p2p_for_downloading);
+  EXPECT_EQ(omaha_disable_p2p_for_sharing,
+            response.disable_p2p_for_sharing);
+
+  EXPECT_EQ(expected_allow_p2p_for_downloading,
+            actual_allow_p2p_for_downloading);
+  EXPECT_EQ(expected_allow_p2p_for_sharing, actual_allow_p2p_for_sharing);
+  EXPECT_EQ(expected_p2p_url, actual_p2p_url);
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeer) {
+  P2PTest(true,                   // initial_allow_p2p_for_downloading
+          true,                   // initial_allow_p2p_for_sharing
+          false,                  // omaha_disable_p2p_for_downloading
+          false,                  // omaha_disable_p2p_for_sharing
+          true,                   // payload_state_allow_p2p_attempt
+          true,                   // expect_p2p_client_lookup
+          "http://1.3.5.7/p2p",   // p2p_client_result_url
+          true,                   // expected_allow_p2p_for_downloading
+          true,                   // expected_allow_p2p_for_sharing
+          "http://1.3.5.7/p2p");  // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithoutPeer) {
+  P2PTest(true,                   // initial_allow_p2p_for_downloading
+          true,                   // initial_allow_p2p_for_sharing
+          false,                  // omaha_disable_p2p_for_downloading
+          false,                  // omaha_disable_p2p_for_sharing
+          true,                   // payload_state_allow_p2p_attempt
+          true,                   // expect_p2p_client_lookup
+          "",                     // p2p_client_result_url
+          false,                  // expected_allow_p2p_for_downloading
+          true,                   // expected_allow_p2p_for_sharing
+          "");                    // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PDownloadNotAllowed) {
+  P2PTest(false,                  // initial_allow_p2p_for_downloading
+          true,                   // initial_allow_p2p_for_sharing
+          false,                  // omaha_disable_p2p_for_downloading
+          false,                  // omaha_disable_p2p_for_sharing
+          true,                   // payload_state_allow_p2p_attempt
+          false,                  // expect_p2p_client_lookup
+          "unset",                // p2p_client_result_url
+          false,                  // expected_allow_p2p_for_downloading
+          true,                   // expected_allow_p2p_for_sharing
+          "");                    // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerDownloadDisabledByOmaha) {
+  P2PTest(true,                   // initial_allow_p2p_for_downloading
+          true,                   // initial_allow_p2p_for_sharing
+          true,                   // omaha_disable_p2p_for_downloading
+          false,                  // omaha_disable_p2p_for_sharing
+          true,                   // payload_state_allow_p2p_attempt
+          false,                  // expect_p2p_client_lookup
+          "unset",                // p2p_client_result_url
+          false,                  // expected_allow_p2p_for_downloading
+          true,                   // expected_allow_p2p_for_sharing
+          "");                    // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerSharingDisabledByOmaha) {
+  P2PTest(true,                   // initial_allow_p2p_for_downloading
+          true,                   // initial_allow_p2p_for_sharing
+          false,                  // omaha_disable_p2p_for_downloading
+          true,                   // omaha_disable_p2p_for_sharing
+          true,                   // payload_state_allow_p2p_attempt
+          true,                   // expect_p2p_client_lookup
+          "http://1.3.5.7/p2p",   // p2p_client_result_url
+          true,                   // expected_allow_p2p_for_downloading
+          false,                  // expected_allow_p2p_for_sharing
+          "http://1.3.5.7/p2p");  // expected_p2p_url
+}
+
+TEST_F(OmahaRequestActionTest, P2PWithPeerBothDisabledByOmaha) {
+  P2PTest(true,                   // initial_allow_p2p_for_downloading
+          true,                   // initial_allow_p2p_for_sharing
+          true,                   // omaha_disable_p2p_for_downloading
+          true,                   // omaha_disable_p2p_for_sharing
+          true,                   // payload_state_allow_p2p_attempt
+          false,                  // expect_p2p_client_lookup
+          "unset",                // p2p_client_result_url
+          false,                  // expected_allow_p2p_for_downloading
+          false,                  // expected_allow_p2p_for_sharing
+          "");                    // expected_p2p_url
+}
+
+bool OmahaRequestActionTest::InstallDateParseHelper(const string &elapsed_days,
+                                                    OmahaResponse *response) {
+  fake_update_response_.elapsed_days = elapsed_days;
+  return
+      TestUpdateCheck(nullptr,  // request_params
+                      fake_update_response_.GetUpdateResponse(),
+                      -1,
+                      false,  // ping_only
+                      ErrorCode::kSuccess,
+                      metrics::CheckResult::kUpdateAvailable,
+                      metrics::CheckReaction::kUpdating,
+                      metrics::DownloadErrorCode::kUnset,
+                      response,
+                      nullptr);
+}
+
+TEST_F(OmahaRequestActionTest, ParseInstallDateFromResponse) {
+  OmahaResponse response;
+
+  // Check that we parse elapsed_days in the Omaha Response correctly.
+  // and that the kPrefsInstallDateDays value is written to.
+  EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+  EXPECT_TRUE(InstallDateParseHelper("42", &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(42, response.install_date_days);
+  EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays));
+  int64_t prefs_days;
+  EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 42);
+
+  // If there already is a value set, we shouldn't do anything.
+  EXPECT_TRUE(InstallDateParseHelper("7", &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(7, response.install_date_days);
+  EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 42);
+
+  // Note that elapsed_days is not necessarily divisible by 7 so check
+  // that we round down correctly when populating kPrefsInstallDateDays.
+  EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays));
+  EXPECT_TRUE(InstallDateParseHelper("23", &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(23, response.install_date_days);
+  EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 21);
+
+  // Check that we correctly handle elapsed_days not being included in
+  // the Omaha Response.
+  EXPECT_TRUE(InstallDateParseHelper("", &response));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(-1, response.install_date_days);
+}
+
+// If there is no prefs and OOBE is not complete, we should not
+// report anything to Omaha.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE) {
+  EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1);
+  EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+}
+
+// If OOBE is complete and happened on a valid date (e.g. after Jan
+// 1 2007 0:00 PST), that date should be used and written to
+// prefs. However, first try with an invalid date and check we do
+// nothing.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithInvalidDate) {
+  Time oobe_date = Time::FromTimeT(42);  // Dec 31, 1969 16:00:42 PST.
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+  EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1);
+  EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays));
+}
+
+// Then check with a valid date. The date Jan 20, 2007 0:00 PST
+// should yield an InstallDate of 14.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithValidDate) {
+  Time oobe_date = Time::FromTimeT(1169280000);  // Jan 20, 2007 0:00 PST.
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+  EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14);
+  EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays));
+
+  int64_t prefs_days;
+  EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 14);
+}
+
+// Now that we have a valid date in prefs, check that we keep using
+// that even if OOBE date reports something else. The date Jan 30,
+// 2007 0:00 PST should yield an InstallDate of 28... but since
+// there's a prefs file, we should still get 14.
+TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedDateChanges) {
+  // Set a valid date in the prefs first.
+  EXPECT_TRUE(fake_prefs_.SetInt64(kPrefsInstallDateDays, 14));
+
+  Time oobe_date = Time::FromTimeT(1170144000);  // Jan 30, 2007 0:00 PST.
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date);
+  EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14);
+
+  int64_t prefs_days;
+  EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 14);
+
+  // If we delete the prefs file, we should get 28 days.
+  EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays));
+  EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 28);
+  EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days));
+  EXPECT_EQ(prefs_days, 28);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
new file mode 100644
index 0000000..abbd012
--- /dev/null
+++ b/omaha_request_params.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2011 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/omaha_request_params.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/utsname.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <chromeos/key_value_store.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+#define CALL_MEMBER_FN(object, member) ((object).*(member))
+
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const char kProductionOmahaUrl[] =
+    "https://tools.google.com/service/update2";
+const char kAUTestOmahaUrl[] =
+    "https://omaha.sandbox.google.com/service/update2";
+
+const char OmahaRequestParams::kAppId[] =
+    "{87efface-864d-49a5-9bb3-4b050a7c227a}";
+const char OmahaRequestParams::kOsPlatform[] = "Chrome OS";
+const char OmahaRequestParams::kOsVersion[] = "Indy";
+const char OmahaRequestParams::kUpdateChannelKey[] = "CHROMEOS_RELEASE_TRACK";
+const char OmahaRequestParams::kIsPowerwashAllowedKey[] =
+    "CHROMEOS_IS_POWERWASH_ALLOWED";
+const char OmahaRequestParams::kAutoUpdateServerKey[] = "CHROMEOS_AUSERVER";
+
+const char* kChannelsByStability[] = {
+    // This list has to be sorted from least stable to most stable channel.
+    "canary-channel",
+    "dev-channel",
+    "beta-channel",
+    "stable-channel",
+};
+
+bool OmahaRequestParams::Init(const string& in_app_version,
+                              const string& in_update_url,
+                              bool in_interactive) {
+  LOG(INFO) << "Initializing parameters for this update attempt";
+  InitFromLsbValue();
+  bool stateful_override = !ShouldLockDown();
+  os_platform_ = OmahaRequestParams::kOsPlatform;
+  os_version_ = OmahaRequestParams::kOsVersion;
+  app_version_ = in_app_version.empty() ?
+      GetLsbValue("CHROMEOS_RELEASE_VERSION", "", nullptr, stateful_override) :
+      in_app_version;
+  os_sp_ = app_version_ + "_" + GetMachineType();
+  os_board_ = GetLsbValue("CHROMEOS_RELEASE_BOARD",
+                          "",
+                          nullptr,
+                          stateful_override);
+  string release_app_id = GetLsbValue("CHROMEOS_RELEASE_APPID",
+                                      OmahaRequestParams::kAppId,
+                                      nullptr,
+                                      stateful_override);
+  board_app_id_ = GetLsbValue("CHROMEOS_BOARD_APPID",
+                              release_app_id,
+                              nullptr,
+                              stateful_override);
+  canary_app_id_ = GetLsbValue("CHROMEOS_CANARY_APPID",
+                               release_app_id,
+                               nullptr,
+                               stateful_override);
+  app_lang_ = "en-US";
+  hwid_ = system_state_->hardware()->GetHardwareClass();
+  if (CollectECFWVersions()) {
+    fw_version_ = system_state_->hardware()->GetFirmwareVersion();
+    ec_version_ = system_state_->hardware()->GetECVersion();
+  }
+
+  if (current_channel_ == target_channel_) {
+    // deltas are only okay if the /.nodelta file does not exist.  if we don't
+    // know (i.e. stat() returns some unexpected error), then err on the side of
+    // caution and say deltas are not okay.
+    struct stat stbuf;
+    delta_okay_ = (stat((root_ + "/.nodelta").c_str(), &stbuf) < 0) &&
+                  (errno == ENOENT);
+
+  } else {
+    LOG(INFO) << "Disabling deltas as a channel change is pending";
+    // For now, disable delta updates if the current channel is different from
+    // the channel that we're sending to the update server because such updates
+    // are destined to fail -- the current rootfs hash will be different than
+    // the expected hash due to the different channel in /etc/lsb-release.
+    delta_okay_ = false;
+  }
+
+  if (in_update_url.empty())
+    update_url_ = GetLsbValue(kAutoUpdateServerKey, kProductionOmahaUrl,
+                              nullptr, stateful_override);
+  else
+    update_url_ = in_update_url;
+
+  // Set the interactive flag accordingly.
+  interactive_ = in_interactive;
+  return true;
+}
+
+bool OmahaRequestParams::IsUpdateUrlOfficial() const {
+  return (update_url_ == kAUTestOmahaUrl ||
+          update_url_ == GetLsbValue(kAutoUpdateServerKey, kProductionOmahaUrl,
+                                     nullptr, !ShouldLockDown()));
+}
+
+bool OmahaRequestParams::CollectECFWVersions() const {
+  return base::StartsWithASCII(hwid_, string("SAMS ALEX"), true) ||
+         base::StartsWithASCII(hwid_, string("BUTTERFLY"), true) ||
+         base::StartsWithASCII(hwid_, string("LUMPY"), true) ||
+         base::StartsWithASCII(hwid_, string("PARROT"), true) ||
+         base::StartsWithASCII(hwid_, string("SPRING"), true) ||
+         base::StartsWithASCII(hwid_, string("SNOW"), true);
+}
+
+bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel,
+                                          bool is_powerwash_allowed) {
+  LOG(INFO) << "SetTargetChannel called with " << new_target_channel
+            << ", Is Powerwash Allowed = "
+            << utils::ToString(is_powerwash_allowed)
+            << ". Current channel = " << current_channel_
+            << ", existing target channel = " << target_channel_
+            << ", download channel = " << download_channel_;
+  TEST_AND_RETURN_FALSE(IsValidChannel(new_target_channel));
+  chromeos::KeyValueStore lsb_release;
+  base::FilePath kFile(root_ + kStatefulPartition + "/etc/lsb-release");
+
+  lsb_release.Load(kFile);
+  lsb_release.SetString(kUpdateChannelKey, new_target_channel);
+  lsb_release.SetBoolean(kIsPowerwashAllowedKey, is_powerwash_allowed);
+
+  TEST_AND_RETURN_FALSE(base::CreateDirectory(kFile.DirName()));
+  TEST_AND_RETURN_FALSE(lsb_release.Save(kFile));
+  target_channel_ = new_target_channel;
+  is_powerwash_allowed_ = is_powerwash_allowed;
+  return true;
+}
+
+void OmahaRequestParams::SetTargetChannelFromLsbValue() {
+  string target_channel_new_value = GetLsbValue(
+      kUpdateChannelKey,
+      current_channel_,
+      &chromeos_update_engine::OmahaRequestParams::IsValidChannel,
+      true);  // stateful_override
+
+  if (target_channel_ != target_channel_new_value) {
+    target_channel_ = target_channel_new_value;
+    LOG(INFO) << "Target Channel set to " << target_channel_
+              << " from LSB file";
+  }
+}
+
+void OmahaRequestParams::SetCurrentChannelFromLsbValue() {
+  string current_channel_new_value = GetLsbValue(
+      kUpdateChannelKey,
+      current_channel_,
+      nullptr,  // No need to validate the read-only rootfs channel.
+      false);  // stateful_override is false so we get the current channel.
+
+  if (current_channel_ != current_channel_new_value) {
+    current_channel_ = current_channel_new_value;
+    LOG(INFO) << "Current Channel set to " << current_channel_
+              << " from LSB file in rootfs";
+  }
+}
+
+void OmahaRequestParams::SetIsPowerwashAllowedFromLsbValue() {
+  string is_powerwash_allowed_str = GetLsbValue(
+      kIsPowerwashAllowedKey,
+      "false",
+      nullptr,  // no need to validate
+      true);  // always get it from stateful, as that's the only place it'll be
+  bool is_powerwash_allowed_new_value = (is_powerwash_allowed_str == "true");
+  if (is_powerwash_allowed_ != is_powerwash_allowed_new_value) {
+    is_powerwash_allowed_ = is_powerwash_allowed_new_value;
+    LOG(INFO) << "Powerwash Allowed set to "
+              << utils::ToString(is_powerwash_allowed_)
+              << " from LSB file in stateful";
+  }
+}
+
+void OmahaRequestParams::UpdateDownloadChannel() {
+  if (download_channel_ != target_channel_) {
+    download_channel_ = target_channel_;
+    LOG(INFO) << "Download channel for this attempt = " << download_channel_;
+  }
+}
+
+void OmahaRequestParams::InitFromLsbValue() {
+  SetCurrentChannelFromLsbValue();
+  SetTargetChannelFromLsbValue();
+  SetIsPowerwashAllowedFromLsbValue();
+  UpdateDownloadChannel();
+}
+
+string OmahaRequestParams::GetLsbValue(const string& key,
+                                       const string& default_value,
+                                       ValueValidator validator,
+                                       bool stateful_override) const {
+  vector<string> files;
+  if (stateful_override) {
+    files.push_back(string(kStatefulPartition) + "/etc/lsb-release");
+  }
+  files.push_back("/etc/lsb-release");
+  for (vector<string>::const_iterator it = files.begin();
+       it != files.end(); ++it) {
+    // TODO(adlr): make sure files checked are owned as root (and all their
+    // parents are recursively, too).
+    chromeos::KeyValueStore data;
+    if (!data.Load(base::FilePath(root_ + *it)))
+      continue;
+
+    string value;
+    if (data.GetString(key, &value)) {
+      if (validator && !CALL_MEMBER_FN(*this, validator)(value)) {
+        continue;
+      }
+      return value;
+    }
+  }
+  // not found
+  return default_value;
+}
+
+string OmahaRequestParams::GetMachineType() const {
+  struct utsname buf;
+  string ret;
+  if (uname(&buf) == 0)
+    ret = buf.machine;
+  return ret;
+}
+
+bool OmahaRequestParams::ShouldLockDown() const {
+  if (force_lock_down_) {
+    return forced_lock_down_;
+  }
+  return system_state_->hardware()->IsOfficialBuild() &&
+            system_state_->hardware()->IsNormalBootMode();
+}
+
+bool OmahaRequestParams::IsValidChannel(const string& channel) const {
+  return GetChannelIndex(channel) >= 0;
+}
+
+void OmahaRequestParams::set_root(const string& root) {
+  root_ = root;
+  InitFromLsbValue();
+}
+
+void OmahaRequestParams::SetLockDown(bool lock) {
+  force_lock_down_ = true;
+  forced_lock_down_ = lock;
+}
+
+int OmahaRequestParams::GetChannelIndex(const string& channel) const {
+  for (size_t t = 0; t < arraysize(kChannelsByStability); ++t)
+    if (channel == kChannelsByStability[t])
+      return t;
+
+  return -1;
+}
+
+bool OmahaRequestParams::to_more_stable_channel() const {
+  int current_channel_index = GetChannelIndex(current_channel_);
+  int download_channel_index = GetChannelIndex(download_channel_);
+
+  return download_channel_index > current_channel_index;
+}
+
+string OmahaRequestParams::GetAppId() const {
+  return download_channel_ == "canary-channel" ? canary_app_id_ : board_app_id_;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_request_params.h b/omaha_request_params.h
new file mode 100644
index 0000000..345f5c2
--- /dev/null
+++ b/omaha_request_params.h
@@ -0,0 +1,370 @@
+// Copyright (c) 2012 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_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+// This gathers local system information and prepares info used by the
+// Omaha request action.
+
+namespace chromeos_update_engine {
+
+// The default "official" Omaha update URL.
+extern const char kProductionOmahaUrl[];
+
+// The autoupdate test Omaha update URL.
+extern const char kAUTestOmahaUrl[];
+
+class SystemState;
+
+// This class encapsulates the data Omaha gets for the request, along with
+// essential state needed for the processing of the request/response.  The
+// strings in this struct should not be XML escaped.
+//
+// TODO(jaysri): chromium-os:39752 tracks the need to rename this class to
+// reflect its lifetime more appropriately.
+class OmahaRequestParams {
+ public:
+  explicit OmahaRequestParams(SystemState* system_state)
+      : system_state_(system_state),
+        os_platform_(kOsPlatform),
+        os_version_(kOsVersion),
+        board_app_id_(kAppId),
+        canary_app_id_(kAppId),
+        delta_okay_(true),
+        interactive_(false),
+        wall_clock_based_wait_enabled_(false),
+        update_check_count_wait_enabled_(false),
+        min_update_checks_needed_(kDefaultMinUpdateChecks),
+        max_update_checks_allowed_(kDefaultMaxUpdateChecks),
+        is_powerwash_allowed_(false),
+        force_lock_down_(false),
+        forced_lock_down_(false) {
+    InitFromLsbValue();
+  }
+
+  OmahaRequestParams(SystemState* system_state,
+                     const std::string& in_os_platform,
+                     const std::string& in_os_version,
+                     const std::string& in_os_sp,
+                     const std::string& in_os_board,
+                     const std::string& in_app_id,
+                     const std::string& in_app_version,
+                     const std::string& in_app_lang,
+                     const std::string& in_target_channel,
+                     const std::string& in_hwid,
+                     const std::string& in_fw_version,
+                     const std::string& in_ec_version,
+                     bool in_delta_okay,
+                     bool in_interactive,
+                     const std::string& in_update_url,
+                     const std::string& in_target_version_prefix)
+      : system_state_(system_state),
+        os_platform_(in_os_platform),
+        os_version_(in_os_version),
+        os_sp_(in_os_sp),
+        os_board_(in_os_board),
+        board_app_id_(in_app_id),
+        canary_app_id_(in_app_id),
+        app_version_(in_app_version),
+        app_lang_(in_app_lang),
+        current_channel_(in_target_channel),
+        target_channel_(in_target_channel),
+        hwid_(in_hwid),
+        fw_version_(in_fw_version),
+        ec_version_(in_ec_version),
+        delta_okay_(in_delta_okay),
+        interactive_(in_interactive),
+        update_url_(in_update_url),
+        target_version_prefix_(in_target_version_prefix),
+        wall_clock_based_wait_enabled_(false),
+        update_check_count_wait_enabled_(false),
+        min_update_checks_needed_(kDefaultMinUpdateChecks),
+        max_update_checks_allowed_(kDefaultMaxUpdateChecks),
+        is_powerwash_allowed_(false),
+        force_lock_down_(false),
+        forced_lock_down_(false) {}
+
+  virtual ~OmahaRequestParams() = default;
+
+  // Setters and getters for the various properties.
+  inline std::string os_platform() const { return os_platform_; }
+  inline std::string os_version() const { return os_version_; }
+  inline std::string os_sp() const { return os_sp_; }
+  inline std::string os_board() const { return os_board_; }
+  inline std::string board_app_id() const { return board_app_id_; }
+  inline std::string canary_app_id() const { return canary_app_id_; }
+  inline std::string app_lang() const { return app_lang_; }
+  inline std::string hwid() const { return hwid_; }
+  inline std::string fw_version() const { return fw_version_; }
+  inline std::string ec_version() const { return ec_version_; }
+
+  inline void set_app_version(const std::string& version) {
+    app_version_ = version;
+  }
+  inline std::string app_version() const { return app_version_; }
+
+  inline std::string current_channel() const { return current_channel_; }
+  inline std::string target_channel() const { return target_channel_; }
+  inline std::string download_channel() const { return download_channel_; }
+
+  // Can client accept a delta ?
+  inline void set_delta_okay(bool ok) { delta_okay_ = ok; }
+  inline bool delta_okay() const { return delta_okay_; }
+
+  // True if this is a user-initiated update check.
+  inline void set_interactive(bool interactive) { interactive_ = interactive; }
+  inline bool interactive() const { return interactive_; }
+
+  inline void set_update_url(const std::string& url) { update_url_ = url; }
+  inline std::string update_url() const { return update_url_; }
+
+  inline void set_target_version_prefix(const std::string& prefix) {
+    target_version_prefix_ = prefix;
+  }
+
+  inline std::string target_version_prefix() const {
+    return target_version_prefix_;
+  }
+
+  inline void set_wall_clock_based_wait_enabled(bool enabled) {
+    wall_clock_based_wait_enabled_ = enabled;
+  }
+  inline bool wall_clock_based_wait_enabled() const {
+    return wall_clock_based_wait_enabled_;
+  }
+
+  inline void set_waiting_period(base::TimeDelta period) {
+    waiting_period_ = period;
+  }
+  base::TimeDelta waiting_period() const { return waiting_period_; }
+
+  inline void set_update_check_count_wait_enabled(bool enabled) {
+    update_check_count_wait_enabled_ = enabled;
+  }
+
+  inline bool update_check_count_wait_enabled() const {
+    return update_check_count_wait_enabled_;
+  }
+
+  inline void set_min_update_checks_needed(int64_t min) {
+    min_update_checks_needed_ = min;
+  }
+  inline int64_t min_update_checks_needed() const {
+    return min_update_checks_needed_;
+  }
+
+  inline void set_max_update_checks_allowed(int64_t max) {
+    max_update_checks_allowed_ = max;
+  }
+  inline int64_t max_update_checks_allowed() const {
+    return max_update_checks_allowed_;
+  }
+
+  // True if we're trying to update to a more stable channel.
+  // i.e. index(target_channel) > index(current_channel).
+  virtual bool to_more_stable_channel() const;
+
+  // Returns the app id corresponding to the current value of the
+  // download channel.
+  virtual std::string GetAppId() const;
+
+  // Suggested defaults
+  static const char kAppId[];
+  static const char kOsPlatform[];
+  static const char kOsVersion[];
+  static const char kUpdateUrl[];
+  static const char kUpdateChannelKey[];
+  static const char kIsPowerwashAllowedKey[];
+  static const char kAutoUpdateServerKey[];
+  static const int64_t kDefaultMinUpdateChecks = 0;
+  static const int64_t kDefaultMaxUpdateChecks = 8;
+
+  // Initializes all the data in the object. Non-empty
+  // |in_app_version| or |in_update_url| prevents automatic detection
+  // of the parameter. Returns true on success, false otherwise.
+  bool Init(const std::string& in_app_version,
+            const std::string& in_update_url,
+            bool in_interactive);
+
+  // Permanently changes the release channel to |channel|. Performs a
+  // powerwash, if required and allowed.
+  // Returns true on success, false otherwise. Note: This call will fail if
+  // there's a channel change pending already. This is to serialize all the
+  // channel changes done by the user in order to avoid having to solve
+  // numerous edge cases around ensuring the powerwash happens as intended in
+  // all such cases.
+  virtual bool SetTargetChannel(const std::string& channel,
+                                bool is_powerwash_allowed);
+
+  // Updates the download channel for this particular attempt from the current
+  // value of target channel.  This method takes a "snapshot" of the current
+  // value of target channel and uses it for all subsequent Omaha requests for
+  // this attempt (i.e. initial request as well as download progress/error
+  // event requests). The snapshot will be updated only when either this method
+  // or Init is called again.
+  virtual void UpdateDownloadChannel();
+
+  virtual bool is_powerwash_allowed() const { return is_powerwash_allowed_; }
+
+  // Check if the provided update URL is official, meaning either the default
+  // autoupdate server or the autoupdate autotest server.
+  virtual bool IsUpdateUrlOfficial() const;
+
+  // For unit-tests.
+  void set_root(const std::string& root);
+  void set_current_channel(const std::string& channel) {
+    current_channel_ = channel;
+  }
+  void set_target_channel(const std::string& channel) {
+    target_channel_ = channel;
+  }
+
+  // Enforce security mode for testing purposes.
+  void SetLockDown(bool lock);
+
+ private:
+  FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
+  FRIEND_TEST(OmahaRequestParamsTest, ShouldLockDownTest);
+  FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
+  FRIEND_TEST(OmahaRequestParamsTest, LsbPreserveTest);
+  FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest);
+
+  // Use a validator that is a non-static member of this class so that its
+  // inputs can be mocked in unit tests (e.g., build type for IsValidChannel).
+  typedef bool(
+      OmahaRequestParams::*ValueValidator)(  // NOLINT(readability/casting)
+      const std::string&) const;
+
+  // Returns true if parameter values should be locked down for security
+  // reasons. If this is an official build running in normal boot mode, all
+  // values except the release channel are parsed only from the read-only rootfs
+  // partition and the channel values are restricted to a pre-approved set.
+  bool ShouldLockDown() const;
+
+  // Returns true if |channel| is a valid channel, false otherwise. This method
+  // restricts the channel value only if the image is official (see
+  // IsOfficialBuild).
+  bool IsValidChannel(const std::string& channel) const;
+
+  // Returns the index of the given channel.
+  int GetChannelIndex(const std::string& channel) const;
+
+  // Returns True if we should store the fw/ec versions based on our hwid_.
+  // Compares hwid to a set of whitelisted prefixes.
+  bool CollectECFWVersions() const;
+
+  // These are individual helper methods to initialize the said properties from
+  // the LSB value.
+  void SetTargetChannelFromLsbValue();
+  void SetCurrentChannelFromLsbValue();
+  void SetIsPowerwashAllowedFromLsbValue();
+
+  // Initializes the required properties from the LSB value.
+  void InitFromLsbValue();
+
+  // Fetches the value for a given key from
+  // /mnt/stateful_partition/etc/lsb-release if possible and |stateful_override|
+  // is true. Failing that, it looks for the key in /etc/lsb-release. If
+  // |validator| is non-null, uses it to validate and ignore invalid values.
+  std::string GetLsbValue(const std::string& key,
+                          const std::string& default_value,
+                          ValueValidator validator,
+                          bool stateful_override) const;
+
+  // Gets the machine type (e.g. "i686").
+  std::string GetMachineType() const;
+
+  // Global system context.
+  SystemState* system_state_;
+
+  // Basic properties of the OS and Application that go into the Omaha request.
+  std::string os_platform_;
+  std::string os_version_;
+  std::string os_sp_;
+  std::string os_board_;
+
+  // The board app id identifies the app id for the board irrespective of the
+  // channel that we're on. The canary app id identifies the app id to be used
+  // iff we're in the canary-channel. These values could be different depending
+  // on how the release tools are implemented.
+  std::string board_app_id_;
+  std::string canary_app_id_;
+
+  std::string app_version_;
+  std::string app_lang_;
+
+  // The three channel values we deal with.
+  // Current channel: is always the channel from /etc/lsb-release. It never
+  // changes. It's just read in during initialization.
+  std::string current_channel_;
+
+  // Target channel: It starts off with the value of current channel. But if
+  // the user changes the channel, then it'll have a different value. If the
+  // user changes multiple times, target channel will always contain the most
+  // recent change and is updated immediately to the user-selected value even
+  // if we're in the middle of a download (as opposed to download channel
+  // which gets updated only at the start of next download)
+  std::string target_channel_;
+
+  // The channel from which we're downloading the payload. This should normally
+  // be the same as target channel. But if the user made another channel change
+  // we started the download, then they'd be different, in which case, we'd
+  // detect elsewhere that the target channel has been changed and cancel the
+  // current download attempt.
+  std::string download_channel_;
+
+  std::string hwid_;  // Hardware Qualification ID of the client
+  std::string fw_version_;  // Chrome OS Firmware Version.
+  std::string ec_version_;  // Chrome OS EC Version.
+  bool delta_okay_;  // If this client can accept a delta
+  bool interactive_;   // Whether this is a user-initiated update check
+
+  // The URL to send the Omaha request to.
+  std::string update_url_;
+
+  // Prefix of the target OS version that the enterprise wants this device
+  // to be pinned to. It's empty otherwise.
+  std::string target_version_prefix_;
+
+  // True if scattering is enabled, in which case waiting_period_ specifies the
+  // amount of absolute time that we've to wait for before sending a request to
+  // Omaha.
+  bool wall_clock_based_wait_enabled_;
+  base::TimeDelta waiting_period_;
+
+  // True if scattering is enabled to denote the number of update checks
+  // we've to skip before we can send a request to Omaha. The min and max
+  // values establish the bounds for a random number to be chosen within that
+  // range to enable such a wait.
+  bool update_check_count_wait_enabled_;
+  int64_t min_update_checks_needed_;
+  int64_t max_update_checks_allowed_;
+
+  // True if we are allowed to do powerwash, if required, on a channel change.
+  bool is_powerwash_allowed_;
+
+  // When reading files, prepend root_ to the paths. Useful for testing.
+  std::string root_;
+
+  // Force security lock down for testing purposes.
+  bool force_lock_down_;
+  bool forced_lock_down_;
+
+  // TODO(jaysri): Uncomment this after fixing unit tests, as part of
+  // chromium-os:39752
+  // DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
new file mode 100644
index 0000000..82d74f1
--- /dev/null
+++ b/omaha_request_params_unittest.cc
@@ -0,0 +1,584 @@
+// Copyright (c) 2011 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/omaha_request_params.h"
+
+#include <stdio.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class OmahaRequestParamsTest : public ::testing::Test {
+ public:
+  OmahaRequestParamsTest() : params_(&fake_system_state_) {}
+
+ protected:
+  // Return true iff the OmahaRequestParams::Init succeeded. If
+  // out is non-null, it's set w/ the generated data.
+  bool DoTest(OmahaRequestParams* out, const string& app_version,
+              const string& omaha_url);
+
+  void SetUp() override {
+    // Create a uniquely named test directory.
+    ASSERT_TRUE(utils::MakeTempDirectory(kTestDirTemplate,
+                                         &test_dir_));
+
+    ASSERT_EQ(0, System(string("mkdir -p ") + test_dir_ + "/etc"));
+    ASSERT_EQ(0, System(string("mkdir -p ") + test_dir_ +
+                        kStatefulPartition + "/etc"));
+    // Create a fresh copy of the params for each test, so there's no
+    // unintended reuse of state across tests.
+    OmahaRequestParams new_params(&fake_system_state_);
+    params_ = new_params;
+    params_.set_root(test_dir_);
+    params_.SetLockDown(false);
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, System(string("rm -rf ") + test_dir_));
+  }
+
+  OmahaRequestParams params_;
+  FakeSystemState fake_system_state_;
+
+  static const char* kTestDirTemplate;
+  string test_dir_;
+};
+
+const char* OmahaRequestParamsTest::kTestDirTemplate =
+  "omaha_request_params-test-XXXXXX";
+
+bool OmahaRequestParamsTest::DoTest(OmahaRequestParams* out,
+                                    const string& app_version,
+                                    const string& omaha_url) {
+  bool success = params_.Init(app_version, omaha_url, false);
+  if (out)
+    *out = params_;
+  return success;
+}
+
+namespace {
+string GetMachineType() {
+  string machine_type;
+  if (!utils::ReadPipe("uname -m", &machine_type))
+    return "";
+  // Strip anything from the first newline char.
+  size_t newline_pos = machine_type.find('\n');
+  if (newline_pos != string::npos)
+    machine_type.erase(newline_pos);
+  return machine_type;
+}
+}  // namespace
+
+TEST_F(OmahaRequestParamsTest, SimpleTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+  EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, AppIDTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_RELEASE_APPID={58c35cef-9d30-476e-9098-ce20377d535d}\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{58c35cef-9d30-476e-9098-ce20377d535d}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+  EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingChannelTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_EQ("", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ConfusingReleaseTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_EQ("", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingVersionTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ForceVersionTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "ForcedVersion", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("ForcedVersion_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("ForcedVersion", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ForcedURLTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", "http://forced.google.com"));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+  EXPECT_EQ("http://forced.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingURLTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+  EXPECT_EQ(kProductionOmahaUrl, out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, NoDeltasTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+  ASSERT_TRUE(WriteFileString(test_dir_ + "/.nodelta", ""));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_FALSE(out.delta_okay());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+      "CHROMEOS_RELEASE_TRACK=beta-channel\n"
+      "CHROMEOS_AUSERVER=https://www.google.com"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("x86-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+  EXPECT_FALSE(out.delta_okay());
+  EXPECT_EQ("beta-channel", out.target_channel());
+  EXPECT_EQ("https://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideLockDownTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=https://www.google.com"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  params_.SetLockDown(true);
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+  EXPECT_FALSE(out.delta_okay());
+  EXPECT_EQ("stable-channel", out.target_channel());
+  EXPECT_EQ("https://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideSameChannelTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("x86-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+  EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  {
+    OmahaRequestParams params(&fake_system_state_);
+    params.set_root(test_dir_);
+    params.SetLockDown(false);
+    EXPECT_TRUE(params.Init("", "", false));
+    params.SetTargetChannel("canary-channel", false);
+    EXPECT_FALSE(params.is_powerwash_allowed());
+  }
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("canary-channel", out.target_channel());
+  EXPECT_FALSE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  {
+    OmahaRequestParams params(&fake_system_state_);
+    params.set_root(test_dir_);
+    params.SetLockDown(false);
+    EXPECT_TRUE(params.Init("", "", false));
+    params.SetTargetChannel("canary-channel", true);
+    EXPECT_TRUE(params.is_powerwash_allowed());
+  }
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("canary-channel", out.target_channel());
+  EXPECT_TRUE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  {
+    OmahaRequestParams params(&fake_system_state_);
+    params.set_root(string("./") + test_dir_);
+    params.SetLockDown(true);
+    EXPECT_TRUE(params.Init("", "", false));
+    params.SetTargetChannel("dogfood-channel", true);
+    EXPECT_FALSE(params.is_powerwash_allowed());
+  }
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("dev-channel", out.target_channel());
+  EXPECT_FALSE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, IsValidChannelTest) {
+  params_.SetLockDown(false);
+  EXPECT_TRUE(params_.IsValidChannel("canary-channel"));
+  EXPECT_TRUE(params_.IsValidChannel("stable-channel"));
+  EXPECT_TRUE(params_.IsValidChannel("beta-channel"));
+  EXPECT_TRUE(params_.IsValidChannel("dev-channel"));
+  EXPECT_FALSE(params_.IsValidChannel("testimage-channel"));
+  EXPECT_FALSE(params_.IsValidChannel("dogfood-channel"));
+  EXPECT_FALSE(params_.IsValidChannel("some-channel"));
+  EXPECT_FALSE(params_.IsValidChannel(""));
+}
+
+TEST_F(OmahaRequestParamsTest, ValidChannelTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  params_.SetLockDown(true);
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("Chrome OS", out.os_platform());
+  EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+  EXPECT_EQ("arm-generic", out.os_board());
+  EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+  EXPECT_EQ("0.2.2.3", out.app_version());
+  EXPECT_EQ("en-US", out.app_lang());
+  EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+  EXPECT_TRUE(out.delta_okay());
+  EXPECT_EQ("dev-channel", out.target_channel());
+  EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelWorks) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  params_.SetLockDown(false);
+
+  // Check LSB value is used by default when SetTargetChannel is not called.
+  params_.Init("", "", false);
+  EXPECT_EQ("dev-channel", params_.target_channel());
+
+  // When an invalid value is set, it should be ignored and the
+  // value from lsb-release should be used instead.
+  params_.Init("", "", false);
+  EXPECT_FALSE(params_.SetTargetChannel("invalid-channel", false));
+  EXPECT_EQ("dev-channel", params_.target_channel());
+
+  // When set to a valid value, it should take effect.
+  params_.Init("", "", false);
+  EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true));
+  EXPECT_EQ("beta-channel", params_.target_channel());
+
+  // When set to the same value, it should be idempotent.
+  params_.Init("", "", false);
+  EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true));
+  EXPECT_EQ("beta-channel", params_.target_channel());
+
+  // When set to a valid value while a change is already pending, it should
+  // succeed.
+  params_.Init("", "", false);
+  EXPECT_TRUE(params_.SetTargetChannel("stable-channel", true));
+  EXPECT_EQ("stable-channel", params_.target_channel());
+
+  // Set a different channel in stateful LSB release.
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+      "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"));
+
+  // When set to a valid value while a change is already pending, it should
+  // succeed.
+  params_.Init("", "", false);
+  EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true));
+  // The target channel should reflect the change, but the download channel
+  // should continue to retain the old value ...
+  EXPECT_EQ("beta-channel", params_.target_channel());
+  EXPECT_EQ("stable-channel", params_.download_channel());
+
+  // ... until we update the download channel explicitly.
+  params_.UpdateDownloadChannel();
+  EXPECT_EQ("beta-channel", params_.download_channel());
+  EXPECT_EQ("beta-channel", params_.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ChannelIndexTest) {
+  int canary = params_.GetChannelIndex("canary-channel");
+  int dev = params_.GetChannelIndex("dev-channel");
+  int beta = params_.GetChannelIndex("beta-channel");
+  int stable = params_.GetChannelIndex("stable-channel");
+  EXPECT_LE(canary, dev);
+  EXPECT_LE(dev, beta);
+  EXPECT_LE(beta, stable);
+
+  // testimage-channel or other names are not recognized, so index will be -1.
+  int testimage = params_.GetChannelIndex("testimage-channel");
+  int bogus = params_.GetChannelIndex("bogus-channel");
+  EXPECT_EQ(-1, testimage);
+  EXPECT_EQ(-1, bogus);
+}
+
+TEST_F(OmahaRequestParamsTest, ToMoreStableChannelFlagTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+      "CHROMEOS_RELEASE_FOO=bar\n"
+      "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+      "CHROMEOS_RELEASE_TRACK=canary-channel\n"
+      "CHROMEOS_AUSERVER=http://www.google.com"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+      "CHROMEOS_AUSERVER=https://www.google.com"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("https://www.google.com", out.update_url());
+  EXPECT_FALSE(out.delta_okay());
+  EXPECT_EQ("stable-channel", out.target_channel());
+  EXPECT_TRUE(out.to_more_stable_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ShouldLockDownTest) {
+  EXPECT_FALSE(params_.ShouldLockDown());
+}
+
+TEST_F(OmahaRequestParamsTest, BoardAppIdUsedForNonCanaryChannelTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_APPID=r\n"
+      "CHROMEOS_BOARD_APPID=b\n"
+      "CHROMEOS_CANARY_APPID=c\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("stable-channel", out.download_channel());
+  EXPECT_EQ("b", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, CanaryAppIdUsedForCanaryChannelTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_APPID=r\n"
+      "CHROMEOS_BOARD_APPID=b\n"
+      "CHROMEOS_CANARY_APPID=c\n"
+      "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("canary-channel", out.download_channel());
+  EXPECT_EQ("c", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, ReleaseAppIdUsedAsDefaultTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_APPID=r\n"
+      "CHROMEOS_CANARY_APPID=c\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  OmahaRequestParams out(&fake_system_state_);
+  EXPECT_TRUE(DoTest(&out, "", ""));
+  EXPECT_EQ("stable-channel", out.download_channel());
+  EXPECT_EQ("r", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) {
+  ASSERT_TRUE(WriteFileString(
+      test_dir_ + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_APPID=r\n"
+      "CHROMEOS_CANARY_APPID=c\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  OmahaRequestParams out(&fake_system_state_);
+  out.hwid_ = string("STUMPY ALEX 12345");
+  EXPECT_FALSE(out.CollectECFWVersions());
+
+  out.hwid_ = string("SNOW 12345");
+  EXPECT_TRUE(out.CollectECFWVersions());
+
+  out.hwid_ = string("SAMS ALEX 12345");
+  EXPECT_TRUE(out.CollectECFWVersions());
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_response.h b/omaha_response.h
new file mode 100644
index 0000000..d58bf46
--- /dev/null
+++ b/omaha_response.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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_OMAHA_RESPONSE_H_
+#define UPDATE_ENGINE_OMAHA_RESPONSE_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+// This struct encapsulates the data Omaha's response for the request.
+// The strings in this struct are not XML escaped.
+struct OmahaResponse {
+  // True iff there is an update to be downloaded.
+  bool update_exists = false;
+
+  // If non-zero, server-dictated poll interval in seconds.
+  int poll_interval = 0;
+
+  // These are only valid if update_exists is true:
+  std::string version;
+
+  // The ordered list of URLs in the Omaha response. Each item is a complete
+  // URL (i.e. in terms of Omaha XML, each value is a urlBase + packageName)
+  std::vector<std::string> payload_urls;
+
+  std::string more_info_url;
+  std::string hash;
+  std::string metadata_signature;
+  std::string deadline;
+  off_t size = 0;
+  off_t metadata_size = 0;
+  int max_days_to_scatter = 0;
+  // The number of URL-related failures to tolerate before moving on to the
+  // next URL in the current pass. This is a configurable value from the
+  // Omaha Response attribute, if ever we need to fine tune the behavior.
+  uint32_t max_failure_count_per_url = 0;
+  bool prompt = false;
+
+  // True if the payload described in this response is a delta payload.
+  // False if it's a full payload.
+  bool is_delta_payload = false;
+
+  // True if the Omaha rule instructs us to disable the back-off logic
+  // on the client altogether. False otherwise.
+  bool disable_payload_backoff = false;
+
+  // True if the Omaha rule instructs us to disable p2p for downloading.
+  bool disable_p2p_for_downloading = false;
+
+  // True if the Omaha rule instructs us to disable p2p for sharing.
+  bool disable_p2p_for_sharing = false;
+
+  // If not blank, a base-64 encoded representation of the PEM-encoded
+  // public key in the response.
+  std::string public_key_rsa;
+
+  // If not -1, the number of days since the epoch Jan 1, 2007 0:00
+  // PST, according to the Omaha Server's clock and timezone (PST8PDT,
+  // aka "Pacific Time".)
+  int install_date_days = -1;
+};
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64bit);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_RESPONSE_H_
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
new file mode 100644
index 0000000..67639cc
--- /dev/null
+++ b/omaha_response_handler_action.cc
@@ -0,0 +1,196 @@
+// Copyright (c) 2011 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/omaha_response_handler_action.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/connection_manager.h"
+#include "update_engine/constants.h"
+#include "update_engine/delta_performer.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char OmahaResponseHandlerAction::kDeadlineFile[] =
+    "/tmp/update-check-response-deadline";
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+    SystemState* system_state)
+    : system_state_(system_state),
+      got_no_update_response_(false),
+      key_path_(DeltaPerformer::kUpdatePayloadPublicKeyPath),
+      deadline_file_(kDeadlineFile) {}
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+    SystemState* system_state, const string& deadline_file)
+    : system_state_(system_state),
+      got_no_update_response_(false),
+      key_path_(DeltaPerformer::kUpdatePayloadPublicKeyPath),
+      deadline_file_(deadline_file) {}
+
+void OmahaResponseHandlerAction::PerformAction() {
+  CHECK(HasInputObject());
+  ScopedActionCompleter completer(processor_, this);
+  const OmahaResponse& response = GetInputObject();
+  if (!response.update_exists) {
+    got_no_update_response_ = true;
+    LOG(INFO) << "There are no updates. Aborting.";
+    return;
+  }
+
+  // All decisions as to which URL should be used have already been done. So,
+  // make the current URL as the download URL.
+  string current_url = system_state_->payload_state()->GetCurrentUrl();
+  if (current_url.empty()) {
+    // This shouldn't happen as we should always supply the HTTPS backup URL.
+    // Handling this anyway, just in case.
+    LOG(ERROR) << "There are no suitable URLs in the response to use.";
+    completer.set_code(ErrorCode::kOmahaResponseInvalid);
+    return;
+  }
+
+  install_plan_.download_url = current_url;
+  install_plan_.version = response.version;
+
+  OmahaRequestParams* const params = system_state_->request_params();
+  PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+  // If we're using p2p to download and there is a local peer, use it.
+  if (payload_state->GetUsingP2PForDownloading() &&
+      !payload_state->GetP2PUrl().empty()) {
+    LOG(INFO) << "Replacing URL " << install_plan_.download_url
+              << " with local URL " << payload_state->GetP2PUrl()
+              << " since p2p is enabled.";
+    install_plan_.download_url = payload_state->GetP2PUrl();
+    payload_state->SetUsingP2PForDownloading(true);
+  }
+
+  // Fill up the other properties based on the response.
+  install_plan_.payload_size = response.size;
+  install_plan_.payload_hash = response.hash;
+  install_plan_.metadata_size = response.metadata_size;
+  install_plan_.metadata_signature = response.metadata_signature;
+  install_plan_.public_key_rsa = response.public_key_rsa;
+  install_plan_.hash_checks_mandatory = AreHashChecksMandatory(response);
+  install_plan_.is_resume =
+      DeltaPerformer::CanResumeUpdate(system_state_->prefs(), response.hash);
+  if (install_plan_.is_resume) {
+    payload_state->UpdateResumed();
+  } else {
+    payload_state->UpdateRestarted();
+    LOG_IF(WARNING, !DeltaPerformer::ResetUpdateProgress(
+        system_state_->prefs(), false))
+        << "Unable to reset the update progress.";
+    LOG_IF(WARNING, !system_state_->prefs()->SetString(
+        kPrefsUpdateCheckResponseHash, response.hash))
+        << "Unable to save the update check response hash.";
+  }
+  install_plan_.is_full_update = !response.is_delta_payload;
+
+  TEST_AND_RETURN(utils::GetInstallDev(
+      (!boot_device_.empty() ? boot_device_ :
+          system_state_->hardware()->BootDevice()),
+      &install_plan_.install_path));
+  install_plan_.kernel_install_path =
+      utils::KernelDeviceOfBootDevice(install_plan_.install_path);
+  install_plan_.source_path = system_state_->hardware()->BootDevice();
+  install_plan_.kernel_source_path =
+      utils::KernelDeviceOfBootDevice(install_plan_.source_path);
+
+  if (params->to_more_stable_channel() && params->is_powerwash_allowed())
+    install_plan_.powerwash_required = true;
+
+
+  TEST_AND_RETURN(HasOutputPipe());
+  if (HasOutputPipe())
+    SetOutputObject(install_plan_);
+  LOG(INFO) << "Using this install plan:";
+  install_plan_.Dump();
+
+  // Send the deadline data (if any) to Chrome through a file. This is a pretty
+  // hacky solution but should be OK for now.
+  //
+  // TODO(petkov): Re-architect this to avoid communication through a
+  // file. Ideally, we would include this information in D-Bus's GetStatus
+  // method and UpdateStatus signal. A potential issue is that update_engine may
+  // be unresponsive during an update download.
+  utils::WriteFile(deadline_file_.c_str(),
+                   response.deadline.data(),
+                   response.deadline.size());
+  chmod(deadline_file_.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+  completer.set_code(ErrorCode::kSuccess);
+}
+
+bool OmahaResponseHandlerAction::AreHashChecksMandatory(
+    const OmahaResponse& response) {
+  // We sometimes need to waive the hash checks in order to download from
+  // sources that don't provide hashes, such as dev server.
+  // At this point UpdateAttempter::IsAnyUpdateSourceAllowed() has already been
+  // checked, so an unofficial update URL won't get this far unless it's OK to
+  // use without a hash. Additionally, we want to always waive hash checks on
+  // unofficial builds (i.e. dev/test images).
+  // The end result is this:
+  //  * Base image:
+  //    - Official URLs require a hash.
+  //    - Unofficial URLs only get this far if the IsAnyUpdateSourceAllowed()
+  //      devmode/debugd checks pass, in which case the hash is waived.
+  //  * Dev/test image:
+  //    - Any URL is allowed through with no hash checking.
+  if (!system_state_->request_params()->IsUpdateUrlOfficial() ||
+      !system_state_->hardware()->IsOfficialBuild()) {
+    // Still do a hash check if a public key is included.
+    if (!response.public_key_rsa.empty()) {
+      // The autoupdate_CatchBadSignatures test checks for this string
+      // in log-files. Keep in sync.
+      LOG(INFO) << "Mandating payload hash checks since Omaha Response "
+                << "for unofficial build includes public RSA key.";
+      return true;
+    } else {
+      LOG(INFO) << "Waiving payload hash checks for unofficial update URL.";
+      return false;
+    }
+  }
+
+  // If we're using p2p, |install_plan_.download_url| may contain a
+  // HTTP URL even if |response.payload_urls| contain only HTTPS URLs.
+  if (!base::StartsWithASCII(install_plan_.download_url, "https://", false)) {
+    LOG(INFO) << "Mandating hash checks since download_url is not HTTPS.";
+    return true;
+  }
+
+  // TODO(jaysri): VALIDATION: For official builds, we currently waive hash
+  // checks for HTTPS until we have rolled out at least once and are confident
+  // nothing breaks. chromium-os:37082 tracks turning this on for HTTPS
+  // eventually.
+
+  // Even if there's a single non-HTTPS URL, make the hash checks as
+  // mandatory because we could be downloading the payload from any URL later
+  // on. It's really hard to do book-keeping based on each byte being
+  // downloaded to see whether we only used HTTPS throughout.
+  for (size_t i = 0; i < response.payload_urls.size(); i++) {
+    if (!base::StartsWithASCII(response.payload_urls[i], "https://", false)) {
+      LOG(INFO) << "Mandating payload hash checks since Omaha response "
+                << "contains non-HTTPS URL(s)";
+      return true;
+    }
+  }
+
+  LOG(INFO) << "Waiving payload hash checks since Omaha response "
+            << "only has HTTPS URL(s)";
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
new file mode 100644
index 0000000..c3e4a68
--- /dev/null
+++ b/omaha_response_handler_action.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2011 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_OMAHA_RESPONSE_HANDLER_ACTION_H_
+#define UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
+
+#include <string>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/system_state.h"
+
+// This class reads in an Omaha response and converts what it sees into
+// an install plan which is passed out.
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerAction;
+
+template<>
+class ActionTraits<OmahaResponseHandlerAction> {
+ public:
+  typedef OmahaResponse InputObjectType;
+  typedef InstallPlan OutputObjectType;
+};
+
+class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> {
+ public:
+  static const char kDeadlineFile[];
+
+  explicit OmahaResponseHandlerAction(SystemState* system_state);
+
+  typedef ActionTraits<OmahaResponseHandlerAction>::InputObjectType
+      InputObjectType;
+  typedef ActionTraits<OmahaResponseHandlerAction>::OutputObjectType
+      OutputObjectType;
+  void PerformAction() override;
+
+  // This is a synchronous action, and thus TerminateProcessing() should
+  // never be called
+  void TerminateProcessing() override { CHECK(false); }
+
+  // For unit-testing
+  void set_boot_device(const std::string& boot_device) {
+    boot_device_ = boot_device;
+  }
+
+  bool GotNoUpdateResponse() const { return got_no_update_response_; }
+  const InstallPlan& install_plan() const { return install_plan_; }
+
+  // Debugging/logging
+  static std::string StaticType() { return "OmahaResponseHandlerAction"; }
+  std::string Type() const override { return StaticType(); }
+  void set_key_path(const std::string& path) { key_path_ = path; }
+
+ private:
+  // Returns true if payload hash checks are mandatory based on the state
+  // of the system and the contents of the Omaha response. False otherwise.
+  bool AreHashChecksMandatory(const OmahaResponse& response);
+
+  // Global system context.
+  SystemState* system_state_;
+
+  // set to non-empty in unit tests
+  std::string boot_device_;
+
+  // The install plan, if we have an update.
+  InstallPlan install_plan_;
+
+  // True only if we got a response and the response said no updates
+  bool got_no_update_response_;
+
+  // Public key path to use for payload verification.
+  std::string key_path_;
+
+  // File used for communication deadline to Chrome.
+  const std::string deadline_file_;
+
+  // Special ctor + friend declarations for testing purposes.
+  OmahaResponseHandlerAction(SystemState* system_state,
+                             const std::string& deadline_file);
+
+  friend class OmahaResponseHandlerActionTest;
+
+  FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+
+  DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
new file mode 100644
index 0000000..bdbf3a7
--- /dev/null
+++ b/omaha_response_handler_action_unittest.cc
@@ -0,0 +1,434 @@
+// Copyright (c) 2011 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/omaha_response_handler_action.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using testing::Return;
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerActionTest : public ::testing::Test {
+ public:
+  // Return true iff the OmahaResponseHandlerAction succeeded.
+  // If out is non-null, it's set w/ the response from the action.
+  bool DoTestCommon(FakeSystemState* fake_system_state,
+                    const OmahaResponse& in,
+                    const string& boot_dev,
+                    const string& deadline_file,
+                    InstallPlan* out);
+  bool DoTest(const OmahaResponse& in,
+              const string& boot_dev,
+              const string& deadline_file,
+              InstallPlan* out);
+};
+
+class OmahaResponseHandlerActionProcessorDelegate
+    : public ActionProcessorDelegate {
+ public:
+  OmahaResponseHandlerActionProcessorDelegate()
+      : code_(ErrorCode::kError),
+        code_set_(false) {}
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == OmahaResponseHandlerAction::StaticType()) {
+      code_ = code;
+      code_set_ = true;
+    }
+  }
+  ErrorCode code_;
+  bool code_set_;
+};
+
+namespace {
+const char* const kLongName =
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+    "-the_update_a.b.c.d_DELTA_.tgz";
+const char* const kBadVersion = "don't update me";
+}  // namespace
+
+bool OmahaResponseHandlerActionTest::DoTestCommon(
+    FakeSystemState* fake_system_state,
+    const OmahaResponse& in,
+    const string& boot_dev,
+    const string& test_deadline_file,
+    InstallPlan* out) {
+  ActionProcessor processor;
+  OmahaResponseHandlerActionProcessorDelegate delegate;
+  processor.set_delegate(&delegate);
+
+  ObjectFeederAction<OmahaResponse> feeder_action;
+  feeder_action.set_obj(in);
+  if (in.update_exists && in.version != kBadVersion) {
+    EXPECT_CALL(*(fake_system_state->mock_prefs()),
+                SetString(kPrefsUpdateCheckResponseHash, in.hash))
+        .WillOnce(Return(true));
+  }
+
+  string current_url = in.payload_urls.size() ? in.payload_urls[0] : "";
+  EXPECT_CALL(*(fake_system_state->mock_payload_state()), GetCurrentUrl())
+      .WillRepeatedly(Return(current_url));
+
+  OmahaResponseHandlerAction response_handler_action(
+      fake_system_state,
+      (test_deadline_file.empty() ?
+       OmahaResponseHandlerAction::kDeadlineFile : test_deadline_file));
+  response_handler_action.set_boot_device(boot_dev);
+  BondActions(&feeder_action, &response_handler_action);
+  ObjectCollectorAction<InstallPlan> collector_action;
+  BondActions(&response_handler_action, &collector_action);
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&response_handler_action);
+  processor.EnqueueAction(&collector_action);
+  processor.StartProcessing();
+  EXPECT_TRUE(!processor.IsRunning())
+      << "Update test to handle non-async actions";
+  if (out)
+    *out = collector_action.object();
+  EXPECT_TRUE(delegate.code_set_);
+  return delegate.code_ == ErrorCode::kSuccess;
+}
+
+bool OmahaResponseHandlerActionTest::DoTest(const OmahaResponse& in,
+                                            const string& boot_dev,
+                                            const string& deadline_file,
+                                            InstallPlan* out) {
+  FakeSystemState fake_system_state;
+  return DoTestCommon(&fake_system_state, in, boot_dev, deadline_file, out);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
+  string test_deadline_file;
+  CHECK(utils::MakeTempFile(
+          "omaha_response_handler_action_unittest-XXXXXX",
+          &test_deadline_file, nullptr));
+  ScopedPathUnlinker deadline_unlinker(test_deadline_file);
+  {
+    OmahaResponse in;
+    in.update_exists = true;
+    in.version = "a.b.c.d";
+    in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+    in.more_info_url = "http://more/info";
+    in.hash = "HASH+";
+    in.size = 12;
+    in.prompt = false;
+    in.deadline = "20101020";
+    InstallPlan install_plan;
+    EXPECT_TRUE(DoTest(in, "/dev/sda3", test_deadline_file, &install_plan));
+    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ("/dev/sda5", install_plan.install_path);
+    string deadline;
+    EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
+    EXPECT_EQ("20101020", deadline);
+    struct stat deadline_stat;
+    EXPECT_EQ(0, stat(test_deadline_file.c_str(), &deadline_stat));
+    EXPECT_EQ(S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
+              deadline_stat.st_mode);
+    EXPECT_EQ(in.version, install_plan.version);
+  }
+  {
+    OmahaResponse in;
+    in.update_exists = true;
+    in.version = "a.b.c.d";
+    in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+    in.more_info_url = "http://more/info";
+    in.hash = "HASHj+";
+    in.size = 12;
+    in.prompt = true;
+    InstallPlan install_plan;
+    EXPECT_TRUE(DoTest(in, "/dev/sda5", test_deadline_file, &install_plan));
+    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ("/dev/sda3", install_plan.install_path);
+    string deadline;
+    EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline) &&
+                deadline.empty());
+    EXPECT_EQ(in.version, install_plan.version);
+  }
+  {
+    OmahaResponse in;
+    in.update_exists = true;
+    in.version = "a.b.c.d";
+    in.payload_urls.push_back(kLongName);
+    in.more_info_url = "http://more/info";
+    in.hash = "HASHj+";
+    in.size = 12;
+    in.prompt = true;
+    in.deadline = "some-deadline";
+    InstallPlan install_plan;
+    EXPECT_TRUE(DoTest(in, "/dev/sda3", test_deadline_file, &install_plan));
+    EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(in.hash, install_plan.payload_hash);
+    EXPECT_EQ("/dev/sda5", install_plan.install_path);
+    string deadline;
+    EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
+    EXPECT_EQ("some-deadline", deadline);
+    EXPECT_EQ(in.version, install_plan.version);
+  }
+}
+
+TEST_F(OmahaResponseHandlerActionTest, NoUpdatesTest) {
+  OmahaResponse in;
+  in.update_exists = false;
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "/dev/sda1", "", &install_plan));
+  EXPECT_EQ("", install_plan.download_url);
+  EXPECT_EQ("", install_plan.payload_hash);
+  EXPECT_EQ("", install_plan.install_path);
+  EXPECT_EQ("", install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("http://test.should/need/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  FakeSystemState fake_system_state;
+  // Hash checks are always skipped for non-official update URLs.
+  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(true));
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_TRUE(install_plan.hash_checks_mandatory);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForUnofficialUpdateUrl) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  FakeSystemState fake_system_state;
+  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(false));
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_FALSE(install_plan.hash_checks_mandatory);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+       HashChecksForOfficialUrlUnofficialBuildTest) {
+  // Official URLs for unofficial builds (dev/test images) don't require hash.
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  FakeSystemState fake_system_state;
+  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(true));
+  fake_system_state.fake_hardware()->SetIsOfficialBuild(false);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_FALSE(install_plan.hash_checks_mandatory);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpsTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("https://test.should.not/need/hash.checks.signed");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  FakeSystemState fake_system_state;
+  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(true));
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_FALSE(install_plan.hash_checks_mandatory);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForBothHttpAndHttpsTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("http://test.should.still/need/hash.checks");
+  in.payload_urls.push_back("https://test.should.still/need/hash.checks");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+  FakeSystemState fake_system_state;
+  EXPECT_CALL(*(fake_system_state.mock_request_params()),
+              IsUpdateUrlOfficial())
+      .WillRepeatedly(Return(true));
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_TRUE(install_plan.hash_checks_mandatory);
+  EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToMoreStableChannelTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("https://MoreStableChannelTest");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHjk";
+  in.size = 15;
+
+  // Create a uniquely named test directory.
+  string test_dir;
+  ASSERT_TRUE(utils::MakeTempDirectory(
+          "omaha_response_handler_action-test-XXXXXX", &test_dir));
+
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+                      kStatefulPartition + "/etc"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+
+  FakeSystemState fake_system_state;
+  OmahaRequestParams params(&fake_system_state);
+  params.set_root(test_dir);
+  params.SetLockDown(false);
+  params.Init("1.2.3.4", "", 0);
+  EXPECT_EQ("canary-channel", params.current_channel());
+  EXPECT_EQ("stable-channel", params.target_channel());
+  EXPECT_TRUE(params.to_more_stable_channel());
+  EXPECT_TRUE(params.is_powerwash_allowed());
+
+  fake_system_state.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_TRUE(install_plan.powerwash_required);
+
+  ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToLessStableChannelTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("https://LessStableChannelTest");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHjk";
+  in.size = 15;
+
+  // Create a uniquely named test directory.
+  string test_dir;
+  ASSERT_TRUE(utils::MakeTempDirectory(
+          "omaha_response_handler_action-test-XXXXXX", &test_dir));
+
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+  ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+                      kStatefulPartition + "/etc"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+  ASSERT_TRUE(WriteFileString(
+      test_dir + kStatefulPartition + "/etc/lsb-release",
+      "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+
+  FakeSystemState fake_system_state;
+  OmahaRequestParams params(&fake_system_state);
+  params.set_root(test_dir);
+  params.SetLockDown(false);
+  params.Init("5.6.7.8", "", 0);
+  EXPECT_EQ("stable-channel", params.current_channel());
+  params.SetTargetChannel("canary-channel", false);
+  EXPECT_EQ("canary-channel", params.target_channel());
+  EXPECT_FALSE(params.to_more_stable_channel());
+  EXPECT_FALSE(params.is_powerwash_allowed());
+
+  fake_system_state.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_FALSE(install_plan.powerwash_required);
+
+  ASSERT_TRUE(test_utils::RecursiveUnlinkDir(test_dir));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, P2PUrlIsUsedAndHashChecksMandatory) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.payload_urls.push_back("https://would.not/cause/hash/checks");
+  in.more_info_url = "http://more/info";
+  in.hash = "HASHj+";
+  in.size = 12;
+
+  FakeSystemState fake_system_state;
+  OmahaRequestParams params(&fake_system_state);
+  // We're using a real OmahaRequestParams object here so we can't mock
+  // IsUpdateUrlOfficial(), but setting the update URL to the AutoUpdate test
+  // server will cause IsUpdateUrlOfficial() to return true.
+  params.set_update_url(kAUTestOmahaUrl);
+  fake_system_state.set_request_params(&params);
+
+  EXPECT_CALL(*fake_system_state.mock_payload_state(),
+              SetUsingP2PForDownloading(true));
+
+  string p2p_url = "http://9.8.7.6/p2p";
+  EXPECT_CALL(*fake_system_state.mock_payload_state(), GetP2PUrl())
+      .WillRepeatedly(Return(p2p_url));
+  EXPECT_CALL(*fake_system_state.mock_payload_state(),
+              GetUsingP2PForDownloading()).WillRepeatedly(Return(true));
+
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTestCommon(&fake_system_state, in, "/dev/sda5", "",
+                           &install_plan));
+  EXPECT_EQ(in.hash, install_plan.payload_hash);
+  EXPECT_EQ(install_plan.download_url, p2p_url);
+  EXPECT_TRUE(install_plan.hash_checks_mandatory);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/p2p_manager.cc b/p2p_manager.cc
new file mode 100644
index 0000000..c72a023
--- /dev/null
+++ b/p2p_manager.cc
@@ -0,0 +1,727 @@
+// Copyright (c) 2013 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.
+
+// This provides access to timestamps with nanosecond resolution in
+// struct stat, See NOTES in stat(2) for details.
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include "update_engine/p2p_manager.h"
+
+#include <attr/xattr.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/falloc.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/glib_utils.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateManager;
+using std::map;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The default p2p directory.
+const char kDefaultP2PDir[] = "/var/cache/p2p";
+
+// The p2p xattr used for conveying the final size of a file - see the
+// p2p ddoc for details.
+const char kCrosP2PFileSizeXAttrName[] = "user.cros-p2p-filesize";
+
+}  // namespace
+
+// The default P2PManager::Configuration implementation.
+class ConfigurationImpl : public P2PManager::Configuration {
+ public:
+  ConfigurationImpl() {}
+
+  FilePath GetP2PDir() override {
+    return FilePath(kDefaultP2PDir);
+  }
+
+  vector<string> GetInitctlArgs(bool is_start) override {
+    vector<string> args;
+    args.push_back("initctl");
+    args.push_back(is_start ? "start" : "stop");
+    args.push_back("p2p");
+    return args;
+  }
+
+  vector<string> GetP2PClientArgs(const string &file_id,
+                                  size_t minimum_size) override {
+    vector<string> args;
+    args.push_back("p2p-client");
+    args.push_back(string("--get-url=") + file_id);
+    args.push_back(StringPrintf("--minimum-size=%zu", minimum_size));
+    return args;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConfigurationImpl);
+};
+
+// The default P2PManager implementation.
+class P2PManagerImpl : public P2PManager {
+ public:
+  P2PManagerImpl(Configuration *configuration,
+                 ClockInterface *clock,
+                 UpdateManager* update_manager,
+                 const string& file_extension,
+                 const int num_files_to_keep,
+                 const TimeDelta& max_file_age);
+
+  // P2PManager methods.
+  void SetDevicePolicy(const policy::DevicePolicy* device_policy) override;
+  bool IsP2PEnabled() override;
+  bool EnsureP2PRunning() override;
+  bool EnsureP2PNotRunning() override;
+  bool PerformHousekeeping() override;
+  void LookupUrlForFile(const string& file_id,
+                        size_t minimum_size,
+                        TimeDelta max_time_to_wait,
+                        LookupCallback callback) override;
+  bool FileShare(const string& file_id,
+                 size_t expected_size) override;
+  FilePath FileGetPath(const string& file_id) override;
+  ssize_t FileGetSize(const string& file_id) override;
+  ssize_t FileGetExpectedSize(const string& file_id) override;
+  bool FileGetVisible(const string& file_id,
+                      bool *out_result) override;
+  bool FileMakeVisible(const string& file_id) override;
+  int CountSharedFiles() override;
+
+ private:
+  // Enumeration for specifying visibility.
+  enum Visibility {
+    kVisible,
+    kNonVisible
+  };
+
+  // Returns "." + |file_extension_| + ".p2p" if |visibility| is
+  // |kVisible|. Returns the same concatenated with ".tmp" otherwise.
+  string GetExt(Visibility visibility);
+
+  // Gets the on-disk path for |file_id| depending on if the file
+  // is visible or not.
+  FilePath GetPath(const string& file_id, Visibility visibility);
+
+  // Utility function used by EnsureP2PRunning() and EnsureP2PNotRunning().
+  bool EnsureP2P(bool should_be_running);
+
+  // Utility function to delete a file given by |path| and log the
+  // path as well as |reason|. Returns false on failure.
+  bool DeleteP2PFile(const FilePath& path, const string& reason);
+
+  // Schedules an async request for tracking changes in P2P enabled status.
+  void ScheduleEnabledStatusChange();
+
+  // An async callback used by the above.
+  void OnEnabledStatusChange(EvalStatus status, const bool& result);
+
+  // The device policy being used or null if no policy is being used.
+  const policy::DevicePolicy* device_policy_ = nullptr;
+
+  // Configuration object.
+  unique_ptr<Configuration> configuration_;
+
+  // Object for telling the time.
+  ClockInterface* clock_;
+
+  // A pointer to the global Update Manager.
+  UpdateManager* update_manager_;
+
+  // A short string unique to the application (for example "cros_au")
+  // used to mark a file as being owned by a particular application.
+  const string file_extension_;
+
+  // If non-zero, this number denotes how many files in /var/cache/p2p
+  // owned by the application (cf. |file_extension_|) to keep after
+  // performing housekeeping.
+  const int num_files_to_keep_;
+
+  // If non-zero, files older than this will not be kept after
+  // performing housekeeping.
+  const TimeDelta max_file_age_;
+
+  // The string ".p2p".
+  static const char kP2PExtension[];
+
+  // The string ".tmp".
+  static const char kTmpExtension[];
+
+  // Whether P2P service may be running; initially, we assume it may be.
+  bool may_be_running_ = true;
+
+  // The current known enabled status of the P2P feature (initialized lazily),
+  // and whether an async status check has been scheduled.
+  bool is_enabled_;
+  bool waiting_for_enabled_status_change_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(P2PManagerImpl);
+};
+
+const char P2PManagerImpl::kP2PExtension[] = ".p2p";
+
+const char P2PManagerImpl::kTmpExtension[] = ".tmp";
+
+P2PManagerImpl::P2PManagerImpl(Configuration *configuration,
+                               ClockInterface *clock,
+                               UpdateManager* update_manager,
+                               const string& file_extension,
+                               const int num_files_to_keep,
+                               const TimeDelta& max_file_age)
+  : clock_(clock),
+    update_manager_(update_manager),
+    file_extension_(file_extension),
+    num_files_to_keep_(num_files_to_keep),
+    max_file_age_(max_file_age) {
+  configuration_.reset(configuration != nullptr ? configuration :
+                       new ConfigurationImpl());
+}
+
+void P2PManagerImpl::SetDevicePolicy(
+    const policy::DevicePolicy* device_policy) {
+  device_policy_ = device_policy;
+}
+
+bool P2PManagerImpl::IsP2PEnabled() {
+  if (!waiting_for_enabled_status_change_) {
+    // Get and store an initial value.
+    if (update_manager_->PolicyRequest(&Policy::P2PEnabled, &is_enabled_) ==
+        EvalStatus::kFailed) {
+      is_enabled_ = false;
+      LOG(ERROR) << "Querying P2P enabled status failed, disabling.";
+    }
+
+    // Track future changes (async).
+    ScheduleEnabledStatusChange();
+  }
+
+  return is_enabled_;
+}
+
+bool P2PManagerImpl::EnsureP2P(bool should_be_running) {
+  int return_code = 0;
+  string output;
+
+  may_be_running_ = true;  // Unless successful, we must be conservative.
+
+  vector<string> args = configuration_->GetInitctlArgs(should_be_running);
+  if (!Subprocess::SynchronousExec(args, &return_code, &output)) {
+    LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args);
+    return false;
+  }
+
+  // If initctl(8) does not exit normally (exit status other than zero), ensure
+  // that the error message is not benign by scanning stderr; this is a
+  // necessity because initctl does not offer actions such as "start if not
+  // running" or "stop if running".
+  // TODO(zeuthen,chromium:277051): Avoid doing this.
+  if (return_code != 0) {
+    const char *expected_error_message = should_be_running ?
+      "initctl: Job is already running: p2p\n" :
+      "initctl: Unknown instance \n";
+    if (output != expected_error_message)
+      return false;
+  }
+
+  may_be_running_ = should_be_running;  // Successful after all.
+  return true;
+}
+
+bool P2PManagerImpl::EnsureP2PRunning() {
+  return EnsureP2P(true);
+}
+
+bool P2PManagerImpl::EnsureP2PNotRunning() {
+  return EnsureP2P(false);
+}
+
+// Returns True if the timestamp in the first pair is greater than the
+// timestamp in the latter. If used with std::sort() this will yield a
+// sequence of elements where newer (high timestamps) elements precede
+// older ones (low timestamps).
+static bool MatchCompareFunc(const pair<FilePath, Time>& a,
+                             const pair<FilePath, Time>& b) {
+  return a.second > b.second;
+}
+
+string P2PManagerImpl::GetExt(Visibility visibility) {
+  string ext = string(".") + file_extension_ + kP2PExtension;
+  switch (visibility) {
+  case kVisible:
+    break;
+  case kNonVisible:
+    ext += kTmpExtension;
+    break;
+  // Don't add a default case to let the compiler warn about newly
+  // added enum values.
+  }
+  return ext;
+}
+
+FilePath P2PManagerImpl::GetPath(const string& file_id, Visibility visibility) {
+  return configuration_->GetP2PDir().Append(file_id + GetExt(visibility));
+}
+
+bool P2PManagerImpl::DeleteP2PFile(const FilePath& path,
+                                   const string& reason) {
+  LOG(INFO) << "Deleting p2p file " << path.value()
+            << " (reason: " << reason << ")";
+  if (unlink(path.value().c_str()) != 0) {
+    PLOG(ERROR) << "Error deleting p2p file " << path.value();
+    return false;
+  }
+  return true;
+}
+
+
+bool P2PManagerImpl::PerformHousekeeping() {
+  // Open p2p dir.
+  FilePath p2p_dir = configuration_->GetP2PDir();
+  const string ext_visible = GetExt(kVisible);
+  const string ext_non_visible = GetExt(kNonVisible);
+
+  bool deletion_failed = false;
+  vector<pair<FilePath, Time>> matches;
+
+  base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+  // Go through all files and collect their mtime.
+  for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+    if (!(base::EndsWith(name.value(), ext_visible, true) ||
+          base::EndsWith(name.value(), ext_non_visible, true)))
+      continue;
+
+    Time time = dir.GetInfo().GetLastModifiedTime();
+
+    // If instructed to keep only files younger than a given age
+    // (|max_file_age_| != 0), delete files satisfying this criteria
+    // right now. Otherwise add it to a list we'll consider for later.
+    if (clock_ != nullptr && max_file_age_ != TimeDelta() &&
+        clock_->GetWallclockTime() - time > max_file_age_) {
+      if (!DeleteP2PFile(name, "file too old"))
+        deletion_failed = true;
+    } else {
+      matches.push_back(std::make_pair(name, time));
+    }
+  }
+
+  // If instructed to only keep N files (|max_files_to_keep_ != 0),
+  // sort list of matches, newest (biggest time) to oldest (lowest
+  // time). Then delete starting at element |num_files_to_keep_|.
+  if (num_files_to_keep_ > 0) {
+    std::sort(matches.begin(), matches.end(), MatchCompareFunc);
+    vector<pair<FilePath, Time>>::const_iterator i;
+    for (i = matches.begin() + num_files_to_keep_; i < matches.end(); ++i) {
+      if (!DeleteP2PFile(i->first, "too many files"))
+        deletion_failed = true;
+    }
+  }
+
+  return !deletion_failed;
+}
+
+// Helper class for implementing LookupUrlForFile().
+class LookupData {
+ public:
+  explicit LookupData(P2PManager::LookupCallback callback)
+    : callback_(callback) {}
+
+  ~LookupData() {
+    if (timeout_task_ != MessageLoop::kTaskIdNull)
+      MessageLoop::current()->CancelTask(timeout_task_);
+    if (child_tag_)
+      Subprocess::Get().KillExec(child_tag_);
+  }
+
+  void InitiateLookup(const vector<string>& cmd, TimeDelta timeout) {
+    // NOTE: if we fail early (i.e. in this method), we need to schedule
+    // an idle to report the error. This is because we guarantee that
+    // the callback is always called from the message loop (this
+    // guarantee is useful for testing).
+
+    // We expect to run just "p2p-client" and find it in the path.
+    child_tag_ = Subprocess::Get().ExecFlags(
+        cmd, G_SPAWN_SEARCH_PATH, false /* redirect stderr */, OnLookupDone,
+        this);
+
+    if (!child_tag_) {
+      LOG(ERROR) << "Error spawning " << utils::StringVectorToString(cmd);
+      ReportErrorAndDeleteInIdle();
+      return;
+    }
+
+    if (timeout > TimeDelta()) {
+      timeout_task_ = MessageLoop::current()->PostDelayedTask(
+          FROM_HERE,
+          Bind(&LookupData::OnTimeout, base::Unretained(this)),
+          timeout);
+    }
+  }
+
+ private:
+  void ReportErrorAndDeleteInIdle() {
+    MessageLoop::current()->PostTask(FROM_HERE, Bind(
+        &LookupData::OnIdleForReportErrorAndDelete,
+        base::Unretained(this)));
+  }
+
+  void OnIdleForReportErrorAndDelete() {
+    ReportError();
+    delete this;
+  }
+
+  void IssueCallback(const string& url) {
+    if (!callback_.is_null())
+      callback_.Run(url);
+  }
+
+  void ReportError() {
+    if (reported_)
+      return;
+    IssueCallback("");
+    reported_ = true;
+  }
+
+  void ReportSuccess(const string& output) {
+    if (reported_)
+      return;
+    string url = output;
+    size_t newline_pos = url.find('\n');
+    if (newline_pos != string::npos)
+      url.resize(newline_pos);
+
+    // Since p2p-client(1) is constructing this URL itself strictly
+    // speaking there's no need to validate it... but, anyway, can't
+    // hurt.
+    if (url.compare(0, 7, "http://") == 0) {
+      IssueCallback(url);
+    } else {
+      LOG(ERROR) << "p2p URL '" << url << "' does not look right. Ignoring.";
+      ReportError();
+    }
+    reported_ = true;
+  }
+
+  static void OnLookupDone(int return_code,
+                           const string& output,
+                           void *user_data) {
+    LookupData *lookup_data = reinterpret_cast<LookupData*>(user_data);
+    lookup_data->child_tag_ = 0;
+    if (return_code != 0) {
+      LOG(INFO) << "Child exited with non-zero exit code "
+                << return_code;
+      lookup_data->ReportError();
+    } else {
+      lookup_data->ReportSuccess(output);
+    }
+    delete lookup_data;
+  }
+
+  void OnTimeout() {
+    timeout_task_ = MessageLoop::kTaskIdNull;
+    ReportError();
+    delete this;
+  }
+
+  P2PManager::LookupCallback callback_;
+
+  // The Subprocess tag of the running process. A value of 0 means that the
+  // process is not running.
+  uint32_t child_tag_{0};
+
+  // The timeout task_id we are waiting on, if any.
+  MessageLoop::TaskId timeout_task_{MessageLoop::kTaskIdNull};
+
+  bool reported_{false};
+};
+
+void P2PManagerImpl::LookupUrlForFile(const string& file_id,
+                                      size_t minimum_size,
+                                      TimeDelta max_time_to_wait,
+                                      LookupCallback callback) {
+  LookupData *lookup_data = new LookupData(callback);
+  string file_id_with_ext = file_id + "." + file_extension_;
+  vector<string> args = configuration_->GetP2PClientArgs(file_id_with_ext,
+                                                         minimum_size);
+  lookup_data->InitiateLookup(args, max_time_to_wait);
+}
+
+bool P2PManagerImpl::FileShare(const string& file_id,
+                               size_t expected_size) {
+  // Check if file already exist.
+  FilePath path = FileGetPath(file_id);
+  if (!path.empty()) {
+    // File exists - double check its expected size though.
+    ssize_t file_expected_size = FileGetExpectedSize(file_id);
+    if (file_expected_size == -1 ||
+        static_cast<size_t>(file_expected_size) != expected_size) {
+      LOG(ERROR) << "Existing p2p file " << path.value()
+                 << " with expected_size=" << file_expected_size
+                 << " does not match the passed in"
+                 << " expected_size=" << expected_size;
+      return false;
+    }
+    return true;
+  }
+
+  // Before creating the file, bail if statvfs(3) indicates that at
+  // least twice the size is not available in P2P_DIR.
+  struct statvfs statvfsbuf;
+  FilePath p2p_dir = configuration_->GetP2PDir();
+  if (statvfs(p2p_dir.value().c_str(), &statvfsbuf) != 0) {
+    PLOG(ERROR) << "Error calling statvfs() for dir " << p2p_dir.value();
+    return false;
+  }
+  size_t free_bytes =
+      static_cast<size_t>(statvfsbuf.f_bsize) * statvfsbuf.f_bavail;
+  if (free_bytes < 2 * expected_size) {
+    // This can easily happen and is worth reporting.
+    LOG(INFO) << "Refusing to allocate p2p file of " << expected_size
+              << " bytes since the directory " << p2p_dir.value()
+              << " only has " << free_bytes
+              << " bytes available and this is less than twice the"
+              << " requested size.";
+    return false;
+  }
+
+  // Okie-dokey looks like enough space is available - create the file.
+  path = GetPath(file_id, kNonVisible);
+  int fd = open(path.value().c_str(), O_CREAT | O_RDWR, 0644);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error creating file with path " << path.value();
+    return false;
+  }
+  ScopedFdCloser fd_closer(&fd);
+
+  // If the final size is known, allocate the file (e.g. reserve disk
+  // space) and set the user.cros-p2p-filesize xattr.
+  if (expected_size != 0) {
+    if (fallocate(fd,
+                  FALLOC_FL_KEEP_SIZE,  // Keep file size as 0.
+                  0,
+                  expected_size) != 0) {
+      if (errno == ENOSYS || errno == EOPNOTSUPP) {
+        // If the filesystem doesn't support the fallocate, keep
+        // going. This is helpful when running unit tests on build
+        // machines with ancient filesystems and/or OSes.
+        PLOG(WARNING) << "Ignoring fallocate(2) failure";
+      } else {
+        // ENOSPC can happen (funky race though, cf. the statvfs() check
+        // above), handle it gracefully, e.g. use logging level INFO.
+        PLOG(INFO) << "Error allocating " << expected_size
+                   << " bytes for file " << path.value();
+        if (unlink(path.value().c_str()) != 0) {
+          PLOG(ERROR) << "Error deleting file with path " << path.value();
+        }
+        return false;
+      }
+    }
+
+    string decimal_size = StringPrintf("%zu", expected_size);
+    if (fsetxattr(fd, kCrosP2PFileSizeXAttrName,
+                  decimal_size.c_str(), decimal_size.size(), 0) != 0) {
+      PLOG(ERROR) << "Error setting xattr " << path.value();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+FilePath P2PManagerImpl::FileGetPath(const string& file_id) {
+  struct stat statbuf;
+  FilePath path;
+
+  path = GetPath(file_id, kVisible);
+  if (stat(path.value().c_str(), &statbuf) == 0) {
+    return path;
+  }
+
+  path = GetPath(file_id, kNonVisible);
+  if (stat(path.value().c_str(), &statbuf) == 0) {
+    return path;
+  }
+
+  path.clear();
+  return path;
+}
+
+bool P2PManagerImpl::FileGetVisible(const string& file_id,
+                                    bool *out_result) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty()) {
+    LOG(ERROR) << "No file for id " << file_id;
+    return false;
+  }
+  if (out_result != nullptr)
+    *out_result = path.MatchesExtension(kP2PExtension);
+  return true;
+}
+
+bool P2PManagerImpl::FileMakeVisible(const string& file_id) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty()) {
+    LOG(ERROR) << "No file for id " << file_id;
+    return false;
+  }
+
+  // Already visible?
+  if (path.MatchesExtension(kP2PExtension))
+    return true;
+
+  LOG_ASSERT(path.MatchesExtension(kTmpExtension));
+  FilePath new_path = path.RemoveExtension();
+  LOG_ASSERT(new_path.MatchesExtension(kP2PExtension));
+  if (rename(path.value().c_str(), new_path.value().c_str()) != 0) {
+    PLOG(ERROR) << "Error renaming " << path.value()
+                << " to " << new_path.value();
+    return false;
+  }
+
+  return true;
+}
+
+ssize_t P2PManagerImpl::FileGetSize(const string& file_id) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty())
+    return -1;
+
+  return utils::FileSize(path.value());
+}
+
+ssize_t P2PManagerImpl::FileGetExpectedSize(const string& file_id) {
+  FilePath path = FileGetPath(file_id);
+  if (path.empty())
+    return -1;
+
+  char ea_value[64] = { 0 };
+  ssize_t ea_size;
+  ea_size = getxattr(path.value().c_str(), kCrosP2PFileSizeXAttrName,
+                     &ea_value, sizeof(ea_value) - 1);
+  if (ea_size == -1) {
+    PLOG(ERROR) << "Error calling getxattr() on file " << path.value();
+    return -1;
+  }
+
+  char* endp = nullptr;
+  long long int val = strtoll(ea_value, &endp, 0);  // NOLINT(runtime/int)
+  if (*endp != '\0') {
+    LOG(ERROR) << "Error parsing the value '" << ea_value
+               << "' of the xattr " << kCrosP2PFileSizeXAttrName
+               << " as an integer";
+    return -1;
+  }
+
+  return val;
+}
+
+int P2PManagerImpl::CountSharedFiles() {
+  int num_files = 0;
+
+  FilePath p2p_dir = configuration_->GetP2PDir();
+  const string ext_visible = GetExt(kVisible);
+  const string ext_non_visible = GetExt(kNonVisible);
+
+  base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+  for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+    if (base::EndsWith(name.value(), ext_visible, true) ||
+        base::EndsWith(name.value(), ext_non_visible, true))
+      num_files += 1;
+  }
+
+  return num_files;
+}
+
+void P2PManagerImpl::ScheduleEnabledStatusChange() {
+  if (waiting_for_enabled_status_change_)
+    return;
+
+  Callback<void(EvalStatus, const bool&)> callback = Bind(
+      &P2PManagerImpl::OnEnabledStatusChange, base::Unretained(this));
+  update_manager_->AsyncPolicyRequest(callback, &Policy::P2PEnabledChanged,
+                                      is_enabled_);
+  waiting_for_enabled_status_change_ = true;
+}
+
+void P2PManagerImpl::OnEnabledStatusChange(EvalStatus status,
+                                           const bool& result) {
+  waiting_for_enabled_status_change_ = false;
+
+  if (status == EvalStatus::kSucceeded) {
+    if (result == is_enabled_) {
+      LOG(WARNING) << "P2P enabled status did not change, which means that it "
+                      "is permanent; not scheduling further checks.";
+      waiting_for_enabled_status_change_ = true;
+      return;
+    }
+
+    is_enabled_ = result;
+
+    // If P2P is running but shouldn't be, make sure it isn't.
+    if (may_be_running_ && !is_enabled_ && !EnsureP2PNotRunning()) {
+      LOG(WARNING) << "Failed to stop P2P service.";
+    }
+  } else {
+    LOG(WARNING)
+        << "P2P enabled tracking failed (possibly timed out); retrying.";
+  }
+
+  ScheduleEnabledStatusChange();
+}
+
+P2PManager* P2PManager::Construct(
+    Configuration *configuration,
+    ClockInterface *clock,
+    UpdateManager* update_manager,
+    const string& file_extension,
+    const int num_files_to_keep,
+    const TimeDelta& max_file_age) {
+  return new P2PManagerImpl(configuration,
+                            clock,
+                            update_manager,
+                            file_extension,
+                            num_files_to_keep,
+                            max_file_age);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/p2p_manager.h b/p2p_manager.h
new file mode 100644
index 0000000..fb07c2e
--- /dev/null
+++ b/p2p_manager.h
@@ -0,0 +1,176 @@
+// Copyright (c) 2013 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_P2P_MANAGER_H_
+#define UPDATE_ENGINE_P2P_MANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/update_manager/update_manager.h"
+
+namespace chromeos_update_engine {
+
+// Interface for sharing and discovering files via p2p.
+class P2PManager {
+ public:
+  // Interface used for P2PManager implementations. The sole reason
+  // for this interface is unit testing.
+  class Configuration {
+   public:
+    virtual ~Configuration() {}
+
+    // Gets the path to the p2p dir being used, e.g. /var/cache/p2p.
+    virtual base::FilePath GetP2PDir() = 0;
+
+    // Gets the argument vector for starting (if |is_start| is True)
+    // resp. stopping (if |is_start| is False) the p2p service
+    // e.g. ["initctl", "start", "p2p"] or ["initctl", "stop", "p2p"].
+    virtual std::vector<std::string> GetInitctlArgs(bool is_start) = 0;
+
+    // Gets the argument vector for invoking p2p-client, e.g.
+    // "p2p-client --get-url=file_id_we_want --minimum-size=42123".
+    virtual std::vector<std::string> GetP2PClientArgs(
+        const std::string& file_id, size_t minimum_size) = 0;
+  };
+
+  virtual ~P2PManager() {}
+
+  // The type for the callback used in LookupUrlForFile().
+  // If the lookup failed, |url| is empty.
+  typedef base::Callback<void(const std::string& url)> LookupCallback;
+
+  // Use the device policy specified by |device_policy|. If this is
+  // null, then no device policy is used.
+  virtual void SetDevicePolicy(const policy::DevicePolicy* device_policy) = 0;
+
+  // Returns true iff P2P is currently allowed for use on this device. This
+  // value is determined and maintained by the Update Manager.
+  virtual bool IsP2PEnabled() = 0;
+
+  // Ensures that the p2p subsystem is running (e.g. starts it if it's
+  // not already running) and blocks until this is so. Returns false
+  // if an error occurred.
+  virtual bool EnsureP2PRunning() = 0;
+
+  // Ensures that the p2p subsystem is not running (e.g. stops it if
+  // it's running) and blocks until this is so. Returns false if an
+  // error occurred.
+  virtual bool EnsureP2PNotRunning() = 0;
+
+  // Cleans up files in /var/cache/p2p owned by this application as
+  // per the |file_extension| and |num_files_to_keep| values passed
+  // when the object was constructed. This may be called even if
+  // the p2p subsystem is not running.
+  virtual bool PerformHousekeeping() = 0;
+
+  // Asynchronously finds a peer that serves the file identified by
+  // |file_id|. If |minimum_size| is non-zero, will find a peer that
+  // has at least that many bytes. When the result is ready |callback|
+  // is called from the default GLib mainloop.
+  //
+  // This operation may take a very long time to complete because part
+  // of the p2p protocol involves waiting for the LAN-wide sum of all
+  // num-connections to drop below a given threshold. However, if
+  // |max_time_to_wait| is non-zero, the operation is guaranteed to
+  // not exceed this duration.
+  //
+  // If the file is not available on the LAN (or if mDNS/DNS-SD is
+  // filtered), this is guaranteed to not take longer than 5 seconds.
+  virtual void LookupUrlForFile(const std::string& file_id,
+                                size_t minimum_size,
+                                base::TimeDelta max_time_to_wait,
+                                LookupCallback callback) = 0;
+
+  // Shares a file identified by |file_id| in the directory
+  // /var/cache/p2p. Initially the file will not be visible, that is,
+  // it will have a .tmp extension and not be shared via p2p. Use the
+  // FileMakeVisible() method to change this.
+  //
+  // If you know the final size of the file, pass it in the
+  // |expected_size| parameter. Otherwise pass zero. If non-zero, the
+  // amount of free space in /var/cache/p2p is checked and if there is
+  // less than twice the amount of space available, this method
+  // fails. Additionally, disk space will be reserved via fallocate(2)
+  // and |expected_size| is written to the user.cros-p2p-filesize
+  // xattr of the created file.
+  //
+  // If the file already exists, true is returned. Any on-disk xattr
+  // is not updated.
+  virtual bool FileShare(const std::string& file_id,
+                         size_t expected_size) = 0;
+
+  // Gets a fully qualified path for the file identified by |file_id|.
+  // If the file has not been shared already using the FileShare()
+  // method, an empty base::FilePath is returned - use FilePath::empty() to
+  // find out.
+  virtual base::FilePath FileGetPath(const std::string& file_id) = 0;
+
+  // Gets the actual size of the file identified by |file_id|. This is
+  // equivalent to reading the value of the st_size field of the
+  // struct stat on the file given by FileGetPath(). Returns -1 if an
+  // error occurs.
+  //
+  // For a file just created with FileShare() this will return 0.
+  virtual ssize_t FileGetSize(const std::string& file_id) = 0;
+
+  // Gets the expected size of the file identified by |file_id|. This
+  // is equivalent to reading the value of the user.cros-p2p-filesize
+  // xattr on the file given by FileGetPath(). Returns -1 if an error
+  // occurs.
+  //
+  // For a file just created with FileShare() this will return the
+  // value of the |expected_size| parameter passed to that method.
+  virtual ssize_t FileGetExpectedSize(const std::string& file_id) = 0;
+
+  // Gets whether the file identified by |file_id| is publicly
+  // visible. If |out_result| is not null, the result is returned
+  // there. Returns false if an error occurs.
+  virtual bool FileGetVisible(const std::string& file_id,
+                              bool *out_result) = 0;
+
+  // Makes the file identified by |file_id| publicly visible
+  // (e.g. removes the .tmp extension). If the file is already
+  // visible, this method does nothing. Returns False if
+  // the method fails or there is no file for |file_id|.
+  virtual bool FileMakeVisible(const std::string& file_id) = 0;
+
+  // Counts the number of shared files used by this application
+  // (cf. the |file_extension parameter|. Returns -1 if an error
+  // occurred.
+  virtual int CountSharedFiles() = 0;
+
+  // Creates a suitable P2PManager instance and initializes the object
+  // so it's ready for use. The |file_extension| parameter is used to
+  // identify your application, use e.g. "cros_au".  If
+  // |configuration| is non-null, the P2PManager will take ownership
+  // of the Configuration object and use it (hence, it must be
+  // heap-allocated).
+  //
+  // The |num_files_to_keep| parameter specifies how many files to
+  // keep after performing housekeeping (cf. the PerformHousekeeping()
+  // method) - pass zero to allow infinitely many files. The
+  // |max_file_age| parameter specifies the maximum file age after
+  // performing housekeeping (pass zero to allow files of any age).
+  static P2PManager* Construct(
+      Configuration *configuration,
+      ClockInterface *clock,
+      chromeos_update_manager::UpdateManager* update_manager,
+      const std::string& file_extension,
+      const int num_files_to_keep,
+      const base::TimeDelta& max_file_age);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_P2P_MANAGER_H_
diff --git a/p2p_manager_unittest.cc b/p2p_manager_unittest.cc
new file mode 100644
index 0000000..8667578
--- /dev/null
+++ b/p2p_manager_unittest.cc
@@ -0,0 +1,502 @@
+// Copyright (c) 2012 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/p2p_manager.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <attr/xattr.h>  // NOLINT - requires typed defined in unistd.h
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+#include "update_engine/update_manager/mock_policy.h"
+#include "update_engine/utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+// Test fixture that sets up a testing configuration (with e.g. a
+// temporary p2p dir) for P2PManager and cleans up when the test is
+// done.
+class P2PManagerTest : public testing::Test {
+ protected:
+  P2PManagerTest() : fake_um_(&fake_clock_) {}
+  ~P2PManagerTest() override {}
+
+  // Derived from testing::Test.
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    test_conf_ = new FakeP2PManagerConfiguration();
+
+    // Allocate and install a mock policy implementation in the fake Update
+    // Manager.  Note that the FakeUpdateManager takes ownership of the policy
+    // object.
+    mock_policy_ = new chromeos_update_manager::MockPolicy(&fake_clock_);
+    fake_um_.set_policy(mock_policy_);
+
+    // Construct the P2P manager under test.
+    manager_.reset(P2PManager::Construct(test_conf_, &fake_clock_, &fake_um_,
+                                         "cros_au", 3,
+                                         TimeDelta::FromDays(5)));
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+  // TODO(deymo): Replace this with a FakeMessageLoop. P2PManager uses glib to
+  // interact with the p2p-client tool, so we need to run a GlibMessageLoop
+  // here.
+  chromeos::GlibMessageLoop loop_;
+
+  // The P2PManager::Configuration instance used for testing.
+  FakeP2PManagerConfiguration *test_conf_;
+
+  FakeClock fake_clock_;
+  chromeos_update_manager::MockPolicy *mock_policy_ = nullptr;
+  chromeos_update_manager::FakeUpdateManager fake_um_;
+
+  unique_ptr<P2PManager> manager_;
+};
+
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value not
+// changing between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndNotChanged) {
+  EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _));
+  EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+  EXPECT_FALSE(manager_->IsP2PEnabled());
+  chromeos::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value changing
+// between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndChanged) {
+  EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<3>(true),
+              Return(chromeos_update_manager::EvalStatus::kSucceeded)));
+  EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, true));
+  EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+  EXPECT_TRUE(manager_->IsP2PEnabled());
+  chromeos::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that we keep the $N newest files with the .$EXT.p2p extension.
+TEST_F(P2PManagerTest, HousekeepingCountLimit) {
+  // Specifically pass 0 for |max_file_age| to allow files of any age. Note that
+  // we need to reallocate the test_conf_ member, whose currently aliased object
+  // will be freed.
+  test_conf_ = new FakeP2PManagerConfiguration();
+  manager_.reset(P2PManager::Construct(
+      test_conf_, &fake_clock_, &fake_um_, "cros_au", 3,
+      TimeDelta() /* max_file_age */));
+  EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+  base::Time start_time = base::Time::FromDoubleT(1246996800.);
+  // Generate files with different timestamps matching our pattern and generate
+  // other files not matching the pattern.
+  for (int n = 0; n < 5; n++) {
+    base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+        "file_%d.cros_au.p2p", n));
+    base::Time file_time = start_time + TimeDelta::FromMinutes(n);
+    EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+    EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+
+    path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+        "file_%d.OTHER.p2p", n));
+    EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+    EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+  }
+  // CountSharedFiles() only counts 'cros_au' files.
+  EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+  EXPECT_TRUE(manager_->PerformHousekeeping());
+
+  // At this point - after HouseKeeping - we should only have
+  // eight files left.
+  for (int n = 0; n < 5; n++) {
+    string file_name;
+    bool expect;
+
+    expect = (n >= 2);
+    file_name = base::StringPrintf(
+        "%s/file_%d.cros_au.p2p",
+         test_conf_->GetP2PDir().value().c_str(), n);
+    EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+    file_name = base::StringPrintf(
+        "%s/file_%d.OTHER.p2p",
+        test_conf_->GetP2PDir().value().c_str(), n);
+    EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+  }
+  // CountSharedFiles() only counts 'cros_au' files.
+  EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+// Check that we keep files with the .$EXT.p2p extension not older
+// than some specificed age (5 days, in this test).
+TEST_F(P2PManagerTest, HousekeepingAgeLimit) {
+  // We set the cutoff time to be 1 billion seconds (01:46:40 UTC on 9
+  // September 2001 - arbitrary number, but constant to avoid test
+  // flakiness) since the epoch and then we put two files before that
+  // date and three files after.
+  base::Time cutoff_time = base::Time::FromTimeT(1000000000);
+  TimeDelta age_limit = TimeDelta::FromDays(5);
+
+  // Set the clock just so files with a timestamp before |cutoff_time|
+  // will be deleted at housekeeping.
+  fake_clock_.SetWallclockTime(cutoff_time + age_limit);
+
+  // Specifically pass 0 for |num_files_to_keep| to allow any number of files.
+  // Note that we need to reallocate the test_conf_ member, whose currently
+  // aliased object will be freed.
+  test_conf_ = new FakeP2PManagerConfiguration();
+  manager_.reset(P2PManager::Construct(
+      test_conf_, &fake_clock_, &fake_um_, "cros_au",
+      0 /* num_files_to_keep */, age_limit));
+  EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+  // Generate files with different timestamps matching our pattern and generate
+  // other files not matching the pattern.
+  for (int n = 0; n < 5; n++) {
+    base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+        "file_%d.cros_au.p2p", n));
+
+    // With five files and aiming for two of them to be before
+    // |cutoff_time|, we distribute it like this:
+    //
+    //  -------- 0 -------- 1 -------- 2 -------- 3 -------- 4 --------
+    //                            |
+    //                       cutoff_time
+    //
+    base::Time file_date = cutoff_time + (n - 2) * TimeDelta::FromDays(1)
+        + TimeDelta::FromHours(12);
+
+    EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+    EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+
+    path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+        "file_%d.OTHER.p2p", n));
+    EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+    EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+  }
+  // CountSharedFiles() only counts 'cros_au' files.
+  EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+  EXPECT_TRUE(manager_->PerformHousekeeping());
+
+  // At this point - after HouseKeeping - we should only have
+  // eight files left.
+  for (int n = 0; n < 5; n++) {
+    string file_name;
+    bool expect;
+
+    expect = (n >= 2);
+    file_name = base::StringPrintf(
+        "%s/file_%d.cros_au.p2p",
+         test_conf_->GetP2PDir().value().c_str(), n);
+    EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+    file_name = base::StringPrintf(
+        "%s/file_%d.OTHER.p2p",
+        test_conf_->GetP2PDir().value().c_str(), n);
+    EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+  }
+  // CountSharedFiles() only counts 'cros_au' files.
+  EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+static bool CheckP2PFile(const string& p2p_dir, const string& file_name,
+                         ssize_t expected_size, ssize_t expected_size_xattr) {
+  string path = p2p_dir + "/" + file_name;
+  char ea_value[64] = { 0 };
+  ssize_t ea_size;
+
+  off_t p2p_size = utils::FileSize(path);
+  if (p2p_size < 0) {
+    LOG(ERROR) << "File " << path << " does not exist";
+    return false;
+  }
+
+  if (expected_size != 0) {
+    if (p2p_size != expected_size) {
+      LOG(ERROR) << "Expected size " << expected_size
+                 << " but size was " << p2p_size;
+      return false;
+    }
+  }
+
+  if (expected_size_xattr == 0) {
+    ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+                       &ea_value, sizeof ea_value - 1);
+    if (ea_size == -1 && errno == ENOATTR) {
+      // This is valid behavior as we support files without the xattr set.
+    } else {
+      PLOG(ERROR) << "getxattr() didn't fail with ENOATTR as expected, "
+                  << "ea_size=" << ea_size << ", errno=" << errno;
+      return false;
+    }
+  } else {
+    ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+                       &ea_value, sizeof ea_value - 1);
+    if (ea_size < 0) {
+      LOG(ERROR) << "Error getting xattr attribute";
+      return false;
+    }
+    char* endp = nullptr;
+    long long int val = strtoll(ea_value, &endp, 0);  // NOLINT(runtime/int)
+    if (endp == nullptr || *endp != '\0') {
+      LOG(ERROR) << "Error parsing xattr '" << ea_value
+                 << "' as an integer";
+      return false;
+    }
+    if (val != expected_size_xattr) {
+      LOG(ERROR) << "Expected xattr size " << expected_size_xattr
+                 << " but size was " << val;
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static bool CreateP2PFile(string p2p_dir, string file_name,
+                          size_t size, size_t size_xattr) {
+  string path = p2p_dir + "/" + file_name;
+
+  int fd = open(path.c_str(), O_CREAT|O_RDWR, 0644);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error creating file with path " << path;
+    return false;
+  }
+  if (ftruncate(fd, size) != 0) {
+    PLOG(ERROR) << "Error truncating " << path << " to size " << size;
+    close(fd);
+    return false;
+  }
+
+  if (size_xattr != 0) {
+    string decimal_size = base::StringPrintf("%zu", size_xattr);
+    if (fsetxattr(fd, "user.cros-p2p-filesize",
+                  decimal_size.c_str(), decimal_size.size(), 0) != 0) {
+      PLOG(ERROR) << "Error setting xattr on " << path;
+      close(fd);
+      return false;
+    }
+  }
+
+  close(fd);
+  return true;
+}
+
+// Check that sharing a *new* file works.
+TEST_F(P2PManagerTest, ShareFile) {
+  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+  const int kP2PTestFileSize = 1000 * 1000;  // 1 MB
+
+  EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+  EXPECT_EQ(manager_->FileGetPath("foo"),
+            test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+  EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+                           "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize));
+
+  // Sharing it again - with the same expected size - should return true
+  EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+
+  // ... but if we use the wrong size, it should fail
+  EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize + 1));
+}
+
+// Check that making a shared file visible, does what is expected.
+TEST_F(P2PManagerTest, MakeFileVisible) {
+  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+  const int kP2PTestFileSize = 1000 * 1000;  // 1 MB
+
+  // First, check that it's not visible.
+  manager_->FileShare("foo", kP2PTestFileSize);
+  EXPECT_EQ(manager_->FileGetPath("foo"),
+            test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+  EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+                           "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize));
+  // Make the file visible and check that it changed its name. Do it
+  // twice to check that FileMakeVisible() is idempotent.
+  for (int n = 0; n < 2; n++) {
+    manager_->FileMakeVisible("foo");
+    EXPECT_EQ(manager_->FileGetPath("foo"),
+              test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+    EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+                             "foo.cros_au.p2p", 0, kP2PTestFileSize));
+  }
+}
+
+// Check that we return the right values for existing files in P2P_DIR.
+TEST_F(P2PManagerTest, ExistingFiles) {
+  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+                 << "Please update your system to support this feature.";
+    return;
+  }
+
+  bool visible;
+
+  // Check that errors are returned if the file does not exist
+  EXPECT_EQ(manager_->FileGetPath("foo"), base::FilePath());
+  EXPECT_EQ(manager_->FileGetSize("foo"), -1);
+  EXPECT_EQ(manager_->FileGetExpectedSize("foo"), -1);
+  EXPECT_FALSE(manager_->FileGetVisible("foo", nullptr));
+  // ... then create the file ...
+  EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+                            "foo.cros_au.p2p", 42, 43));
+  // ... and then check that the expected values are returned
+  EXPECT_EQ(manager_->FileGetPath("foo"),
+            test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+  EXPECT_EQ(manager_->FileGetSize("foo"), 42);
+  EXPECT_EQ(manager_->FileGetExpectedSize("foo"), 43);
+  EXPECT_TRUE(manager_->FileGetVisible("foo", &visible));
+  EXPECT_TRUE(visible);
+
+  // One more time, this time with a .tmp variant. First ensure it errors out..
+  EXPECT_EQ(manager_->FileGetPath("bar"), base::FilePath());
+  EXPECT_EQ(manager_->FileGetSize("bar"), -1);
+  EXPECT_EQ(manager_->FileGetExpectedSize("bar"), -1);
+  EXPECT_FALSE(manager_->FileGetVisible("bar", nullptr));
+  // ... then create the file ...
+  EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+                            "bar.cros_au.p2p.tmp", 44, 45));
+  // ... and then check that the expected values are returned
+  EXPECT_EQ(manager_->FileGetPath("bar"),
+            test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp"));
+  EXPECT_EQ(manager_->FileGetSize("bar"), 44);
+  EXPECT_EQ(manager_->FileGetExpectedSize("bar"), 45);
+  EXPECT_TRUE(manager_->FileGetVisible("bar", &visible));
+  EXPECT_FALSE(visible);
+}
+
+// This is a little bit ugly but short of mocking a 'p2p' service this
+// will have to do. E.g. we essentially simulate the various
+// behaviours of initctl(8) that we rely on.
+TEST_F(P2PManagerTest, StartP2P) {
+  // Check that we can start the service
+  test_conf_->SetInitctlStartCommand({"true"});
+  EXPECT_TRUE(manager_->EnsureP2PRunning());
+  test_conf_->SetInitctlStartCommand({"false"});
+  EXPECT_FALSE(manager_->EnsureP2PRunning());
+  test_conf_->SetInitctlStartCommand({
+      "sh", "-c", "echo \"initctl: Job is already running: p2p\" >&2; false"});
+  EXPECT_TRUE(manager_->EnsureP2PRunning());
+  test_conf_->SetInitctlStartCommand({
+      "sh", "-c", "echo something else >&2; false"});
+  EXPECT_FALSE(manager_->EnsureP2PRunning());
+}
+
+// Same comment as for StartP2P
+TEST_F(P2PManagerTest, StopP2P) {
+  // Check that we can start the service
+  test_conf_->SetInitctlStopCommand({"true"});
+  EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+  test_conf_->SetInitctlStopCommand({"false"});
+  EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+  test_conf_->SetInitctlStopCommand({
+      "sh", "-c", "echo \"initctl: Unknown instance \" >&2; false"});
+  EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+  test_conf_->SetInitctlStopCommand({
+      "sh", "-c", "echo something else >&2; false"});
+  EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+}
+
+static void ExpectUrl(const string& expected_url,
+                      const string& url) {
+  EXPECT_EQ(url, expected_url);
+  MessageLoop::current()->BreakLoop();
+}
+
+// Like StartP2P, we're mocking the different results that p2p-client
+// can return. It's not pretty but it works.
+TEST_F(P2PManagerTest, LookupURL) {
+  // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au"
+  // being propagated in the right places.
+  test_conf_->SetP2PClientCommand({
+      "echo", "http://1.2.3.4/{file_id}_{minsize}"});
+  manager_->LookupUrlForFile("fooX", 42, TimeDelta(),
+                             base::Bind(ExpectUrl,
+                                        "http://1.2.3.4/fooX.cros_au_42"));
+  loop_.Run();
+
+  // Emulate p2p-client returning invalid URL.
+  test_conf_->SetP2PClientCommand({"echo", "not_a_valid_url"});
+  manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+                             base::Bind(ExpectUrl, ""));
+  loop_.Run();
+
+  // Emulate p2p-client conveying failure.
+  test_conf_->SetP2PClientCommand({"false"});
+  manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+                             base::Bind(ExpectUrl, ""));
+  loop_.Run();
+
+  // Emulate p2p-client not existing.
+  test_conf_->SetP2PClientCommand({"/path/to/non/existent/helper/program"});
+  manager_->LookupUrlForFile("foobar", 42,
+                             TimeDelta(),
+                             base::Bind(ExpectUrl, ""));
+  loop_.Run();
+
+  // Emulate p2p-client crashing.
+  test_conf_->SetP2PClientCommand({"sh", "-c", "kill -SEGV $$"});
+  manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+                             base::Bind(ExpectUrl, ""));
+  loop_.Run();
+
+  // Emulate p2p-client exceeding its timeout.
+  test_conf_->SetP2PClientCommand({
+      "sh", "-c", "echo http://1.2.3.4/; sleep 2"});
+  manager_->LookupUrlForFile("foobar", 42, TimeDelta::FromMilliseconds(500),
+                             base::Bind(ExpectUrl, ""));
+  loop_.Run();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_constants.cc b/payload_constants.cc
new file mode 100644
index 0000000..2fc31e3
--- /dev/null
+++ b/payload_constants.cc
@@ -0,0 +1,12 @@
+// Copyright 2014 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_constants.h"
+
+namespace chromeos_update_engine {
+
+const char kDeltaMagic[] = "CrAU";
+const char kBspatchPath[] = "bspatch";
+
+};  // namespace chromeos_update_engine
diff --git a/payload_constants.h b/payload_constants.h
new file mode 100644
index 0000000..30dde5e
--- /dev/null
+++ b/payload_constants.h
@@ -0,0 +1,23 @@
+// Copyright 2014 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_CONSTANTS_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSTANTS_H_
+
+#include <stdint.h>
+
+#include <limits>
+
+namespace chromeos_update_engine {
+
+extern const char kBspatchPath[];
+extern const char kDeltaMagic[];
+
+// A block number denoting a hole on a sparse file. Used on Extents to refer to
+// section of blocks not present on disk on a sparse file.
+const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max();
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_CONSTANTS_H_
diff --git a/payload_generator/ab_generator.cc b/payload_generator/ab_generator.cc
new file mode 100644
index 0000000..67163eb
--- /dev/null
+++ b/payload_generator/ab_generator.cc
@@ -0,0 +1,365 @@
+// 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/ab_generator.h"
+
+#include <algorithm>
+
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Compare two AnnotatedOperations by the start block of the first Extent in
+// their destination extents.
+bool CompareAopsByDestination(AnnotatedOperation first_aop,
+                              AnnotatedOperation second_aop) {
+  // We want empty operations to be at the end of the payload.
+  if (!first_aop.op.dst_extents().size() || !second_aop.op.dst_extents().size())
+    return ((!first_aop.op.dst_extents().size()) <
+            (!second_aop.op.dst_extents().size()));
+  uint32_t first_dst_start = first_aop.op.dst_extents(0).start_block();
+  uint32_t second_dst_start = second_aop.op.dst_extents(0).start_block();
+  return first_dst_start < second_dst_start;
+}
+
+}  // namespace
+
+bool ABGenerator::GenerateOperations(
+    const PayloadGenerationConfig& config,
+    int data_file_fd,
+    off_t* data_file_size,
+    vector<AnnotatedOperation>* rootfs_ops,
+    vector<AnnotatedOperation>* kernel_ops) {
+
+  off_t chunk_blocks = (config.chunk_size == -1 ? -1 :
+                        config.chunk_size / config.block_size);
+
+  rootfs_ops->clear();
+  TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(
+      rootfs_ops,
+      config.source.rootfs,
+      config.target.rootfs,
+      chunk_blocks,
+      data_file_fd,
+      data_file_size,
+      false,  // skip_block_0
+      true));  // src_ops_allowed
+  LOG(INFO) << "done reading normal files";
+
+  // Read kernel partition
+  TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(
+      kernel_ops,
+      config.source.kernel,
+      config.target.kernel,
+      chunk_blocks,
+      data_file_fd,
+      data_file_size,
+      false,  // skip_block_0
+      true));  // src_ops_allowed
+  LOG(INFO) << "done reading kernel";
+
+  TEST_AND_RETURN_FALSE(FragmentOperations(rootfs_ops,
+                                           config.target.rootfs.path,
+                                           data_file_fd,
+                                           data_file_size));
+  TEST_AND_RETURN_FALSE(FragmentOperations(kernel_ops,
+                                           config.target.kernel.path,
+                                           data_file_fd,
+                                           data_file_size));
+  SortOperationsByDestination(rootfs_ops);
+  SortOperationsByDestination(kernel_ops);
+  // TODO(alliewood): Change merge operations to use config.chunk_size once
+  // specifying chunk_size on the command line works. crbug/485397.
+  TEST_AND_RETURN_FALSE(MergeOperations(rootfs_ops,
+                                        kDefaultChunkSize,
+                                        config.target.rootfs.path,
+                                        data_file_fd,
+                                        data_file_size));
+  TEST_AND_RETURN_FALSE(MergeOperations(kernel_ops,
+                                        kDefaultChunkSize,
+                                        config.target.kernel.path,
+                                        data_file_fd,
+                                        data_file_size));
+  return true;
+}
+
+void ABGenerator::SortOperationsByDestination(
+    vector<AnnotatedOperation>* aops) {
+  sort(aops->begin(), aops->end(), CompareAopsByDestination);
+}
+
+bool ABGenerator::FragmentOperations(
+    vector<AnnotatedOperation>* aops,
+    const string& target_part_path,
+    int data_fd,
+    off_t* data_file_size) {
+  vector<AnnotatedOperation> fragmented_aops;
+  for (const AnnotatedOperation& aop : *aops) {
+    if (aop.op.type() ==
+        DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY) {
+      TEST_AND_RETURN_FALSE(SplitSourceCopy(aop, &fragmented_aops));
+    } else if ((aop.op.type() ==
+                DeltaArchiveManifest_InstallOperation_Type_REPLACE) ||
+               (aop.op.type() ==
+                DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ)) {
+      TEST_AND_RETURN_FALSE(SplitReplaceOrReplaceBz(aop, &fragmented_aops,
+                                                    target_part_path, data_fd,
+                                                    data_file_size));
+    } else {
+      fragmented_aops.push_back(aop);
+    }
+  }
+  *aops = fragmented_aops;
+  return true;
+}
+
+bool ABGenerator::SplitSourceCopy(
+    const AnnotatedOperation& original_aop,
+    vector<AnnotatedOperation>* result_aops) {
+  DeltaArchiveManifest_InstallOperation original_op = original_aop.op;
+  TEST_AND_RETURN_FALSE(original_op.type() ==
+                        DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+  // Keeps track of the index of curr_src_ext.
+  int curr_src_ext_index = 0;
+  Extent curr_src_ext = original_op.src_extents(curr_src_ext_index);
+  for (int i = 0; i < original_op.dst_extents_size(); i++) {
+    Extent dst_ext = original_op.dst_extents(i);
+    // The new operation which will have only one dst extent.
+    DeltaArchiveManifest_InstallOperation new_op;
+    uint64_t blocks_left = dst_ext.num_blocks();
+    while (blocks_left > 0) {
+      if (curr_src_ext.num_blocks() <= blocks_left) {
+        // If the curr_src_ext is smaller than dst_ext, add it.
+        blocks_left -= curr_src_ext.num_blocks();
+        *(new_op.add_src_extents()) = curr_src_ext;
+        if (curr_src_ext_index + 1 < original_op.src_extents().size()) {
+          curr_src_ext = original_op.src_extents(++curr_src_ext_index);
+        } else {
+          break;
+        }
+      } else {
+        // Split src_exts that are bigger than the dst_ext we're dealing with.
+        Extent first_ext;
+        first_ext.set_num_blocks(blocks_left);
+        first_ext.set_start_block(curr_src_ext.start_block());
+        *(new_op.add_src_extents()) = first_ext;
+        // Keep the second half of the split op.
+        curr_src_ext.set_num_blocks(curr_src_ext.num_blocks() - blocks_left);
+        curr_src_ext.set_start_block(curr_src_ext.start_block() + blocks_left);
+        blocks_left -= first_ext.num_blocks();
+      }
+    }
+    // Fix up our new operation and add it to the results.
+    new_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+    *(new_op.add_dst_extents()) = dst_ext;
+    new_op.set_src_length(dst_ext.num_blocks() * kBlockSize);
+    new_op.set_dst_length(dst_ext.num_blocks() * kBlockSize);
+
+    AnnotatedOperation new_aop;
+    new_aop.op = new_op;
+    new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+    result_aops->push_back(new_aop);
+  }
+  if (curr_src_ext_index != original_op.src_extents().size() - 1) {
+    LOG(FATAL) << "Incorrectly split SOURCE_COPY operation. Did not use all "
+               << "source extents.";
+  }
+  return true;
+}
+
+bool ABGenerator::SplitReplaceOrReplaceBz(
+    const AnnotatedOperation& original_aop,
+    vector<AnnotatedOperation>* result_aops,
+    const string& target_part_path,
+    int data_fd,
+    off_t* data_file_size) {
+  DeltaArchiveManifest_InstallOperation original_op = original_aop.op;
+  const bool is_replace =
+      original_op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+  TEST_AND_RETURN_FALSE(
+      is_replace ||
+      (original_op.type() ==
+       DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ));
+
+  uint32_t data_offset = original_op.data_offset();
+  for (int i = 0; i < original_op.dst_extents_size(); i++) {
+    Extent dst_ext = original_op.dst_extents(i);
+    // Make a new operation with only one dst extent.
+    DeltaArchiveManifest_InstallOperation new_op;
+    *(new_op.add_dst_extents()) = dst_ext;
+    uint32_t data_size = dst_ext.num_blocks() * kBlockSize;
+    new_op.set_dst_length(data_size);
+    // If this is a REPLACE, attempt to reuse portions of the existing blob.
+    if (is_replace) {
+      new_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+      new_op.set_data_length(data_size);
+      new_op.set_data_offset(data_offset);
+      data_offset += data_size;
+    }
+
+    AnnotatedOperation new_aop;
+    new_aop.op = new_op;
+    new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+    TEST_AND_RETURN_FALSE(AddDataAndSetType(&new_aop, target_part_path, data_fd,
+                                            data_file_size));
+
+    result_aops->push_back(new_aop);
+  }
+  return true;
+}
+
+bool ABGenerator::MergeOperations(vector<AnnotatedOperation>* aops,
+                                  off_t chunk_size,
+                                  const string& target_part_path,
+                                  int data_fd,
+                                  off_t* data_file_size) {
+  vector<AnnotatedOperation> new_aops;
+  for (const AnnotatedOperation& curr_aop : *aops) {
+    if (new_aops.empty()) {
+      new_aops.push_back(curr_aop);
+      continue;
+    }
+    AnnotatedOperation& last_aop = new_aops.back();
+
+    if (last_aop.op.dst_extents_size() <= 0 ||
+        curr_aop.op.dst_extents_size() <= 0) {
+      new_aops.push_back(curr_aop);
+      continue;
+    }
+    uint32_t last_dst_idx = last_aop.op.dst_extents_size() - 1;
+    uint32_t last_end_block =
+        last_aop.op.dst_extents(last_dst_idx).start_block() +
+        last_aop.op.dst_extents(last_dst_idx).num_blocks();
+    uint32_t curr_start_block = curr_aop.op.dst_extents(0).start_block();
+    uint32_t combined_block_count =
+        last_aop.op.dst_extents(last_dst_idx).num_blocks() +
+        curr_aop.op.dst_extents(0).num_blocks();
+    bool good_op_type =
+        curr_aop.op.type() ==
+            DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY ||
+        curr_aop.op.type() ==
+            DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+        curr_aop.op.type() ==
+            DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ;
+    if (good_op_type &&
+        last_aop.op.type() == curr_aop.op.type() &&
+        last_end_block == curr_start_block &&
+        static_cast<off_t>(combined_block_count * kBlockSize) <= chunk_size) {
+      // If the operations have the same type (which is a type that we can
+      // merge), are contiguous, are fragmented to have one destination extent,
+      // and their combined block count would be less than chunk size, merge
+      // them.
+      last_aop.name = base::StringPrintf("%s,%s",
+                                         last_aop.name.c_str(),
+                                         curr_aop.name.c_str());
+
+      ExtendExtents(last_aop.op.mutable_src_extents(),
+                    curr_aop.op.src_extents());
+      if (curr_aop.op.src_length() > 0)
+        last_aop.op.set_src_length(last_aop.op.src_length() +
+                                   curr_aop.op.src_length());
+      ExtendExtents(last_aop.op.mutable_dst_extents(),
+                    curr_aop.op.dst_extents());
+      if (curr_aop.op.dst_length() > 0)
+        last_aop.op.set_dst_length(last_aop.op.dst_length() +
+                                   curr_aop.op.dst_length());
+      // Set the data length to zero so we know to add the blob later.
+      if (curr_aop.op.type() ==
+          DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+          curr_aop.op.type() ==
+          DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+        last_aop.op.set_data_length(0);
+      }
+    } else {
+      // Otherwise just include the extent as is.
+      new_aops.push_back(curr_aop);
+    }
+  }
+
+  // Set the blobs for REPLACE/REPLACE_BZ operations that have been merged.
+  for (AnnotatedOperation& curr_aop : new_aops) {
+    if (curr_aop.op.data_length() == 0 &&
+        (curr_aop.op.type() ==
+            DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+         curr_aop.op.type() ==
+            DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ)) {
+      TEST_AND_RETURN_FALSE(AddDataAndSetType(&curr_aop, target_part_path,
+                                              data_fd, data_file_size));
+    }
+  }
+
+  *aops = new_aops;
+  return true;
+}
+
+bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop,
+                                    const string& target_part_path,
+                                    int data_fd,
+                                    off_t* data_file_size) {
+  TEST_AND_RETURN_FALSE(
+      aop->op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+      aop->op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+
+  chromeos::Blob data(aop->op.dst_length());
+  vector<Extent> dst_extents;
+  ExtentsToVector(aop->op.dst_extents(), &dst_extents);
+  TEST_AND_RETURN_FALSE(utils::ReadExtents(target_part_path,
+                                           dst_extents,
+                                           &data,
+                                           data.size(),
+                                           kBlockSize));
+
+  chromeos::Blob data_bz;
+  TEST_AND_RETURN_FALSE(BzipCompress(data, &data_bz));
+  CHECK(!data_bz.empty());
+
+  chromeos::Blob* data_p = nullptr;
+  DeltaArchiveManifest_InstallOperation_Type new_op_type;
+  if (data_bz.size() < data.size()) {
+    new_op_type = DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ;
+    data_p = &data_bz;
+  } else {
+    new_op_type = DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+    data_p = &data;
+  }
+
+  // If the operation already points to a data blob, check whether it's
+  // identical to the new one, in which case don't add it.
+  if (aop->op.type() == new_op_type &&
+      aop->op.data_length() == data_p->size()) {
+    chromeos::Blob current_data(data_p->size());
+    ssize_t bytes_read;
+    TEST_AND_RETURN_FALSE(utils::PReadAll(data_fd,
+                                          current_data.data(),
+                                          aop->op.data_length(),
+                                          aop->op.data_offset(),
+                                          &bytes_read));
+    TEST_AND_RETURN_FALSE(bytes_read ==
+                          static_cast<ssize_t>(aop->op.data_length()));
+    if (current_data == *data_p)
+      data_p = nullptr;
+  }
+
+  if (data_p) {
+    aop->op.set_type(new_op_type);
+    aop->SetOperationBlob(data_p, data_fd, data_file_size);
+  }
+
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/ab_generator.h b/payload_generator/ab_generator.h
new file mode 100644
index 0000000..d027f22
--- /dev/null
+++ b/payload_generator/ab_generator.h
@@ -0,0 +1,120 @@
+// 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_AB_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/filesystem_interface.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"
+
+namespace chromeos_update_engine {
+
+// The ABGenerator is an operations generator that generates payloads using the
+// A-to-B operations SOURCE_COPY and SOURCE_BSDIFF introduced in the payload
+// minor version 2 format.
+class ABGenerator : public OperationsGenerator {
+ public:
+  ABGenerator() = default;
+
+  // Generate the update payload operations for the kernel and rootfs using
+  // SOURCE_* operations, used for generating deltas for the minor version
+  // 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 |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|.
+  bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      int data_file_fd,
+      off_t* data_file_size,
+      std::vector<AnnotatedOperation>* rootfs_ops,
+      std::vector<AnnotatedOperation>* kernel_ops) override;
+
+  // Split the operations in the vector of AnnotatedOperations |aops|
+  // such that for every operation there is only one dst extent and updates
+  // |aops| with the new list of operations. All kinds of operations are
+  // fragmented except BSDIFF and SOURCE_BSDIFF operations.
+  // The |target_rootfs_part| is the filename of the new image, where the
+  // destination extents refer to. The blobs of the operations in |aops| should
+  // reference the file |data_fd| whose initial size is |*data_file_size|. The
+  // file contents and the value pointed by |data_file_size| are updated if
+  // needed.
+  static bool FragmentOperations(std::vector<AnnotatedOperation>* aops,
+                                 const std::string& target_rootfs_part,
+                                 int data_fd,
+                                 off_t* data_file_size);
+
+  // Takes a vector of AnnotatedOperations |aops| and sorts them by the first
+  // start block in their destination extents. Sets |aops| to a vector of the
+  // sorted operations.
+  static void SortOperationsByDestination(
+      std::vector<AnnotatedOperation>* aops);
+
+  // Takes an SOURCE_COPY install operation, |aop|, and adds one operation for
+  // each dst extent in |aop| to |ops|. The new operations added to |ops| will
+  // have only one dst extent. The src extents are split so the number of blocks
+  // in the src and dst extents are equal.
+  // E.g. we have a SOURCE_COPY operation:
+  //   src extents: [(1, 3), (5, 1), (7, 1)], dst extents: [(2, 2), (6, 3)]
+  // Then we will get 2 new operations:
+  //   1. src extents: [(1, 2)], dst extents: [(2, 2)]
+  //   2. src extents: [(3, 1),(5, 1),(7, 1)], dst extents: [(6, 3)]
+  static bool SplitSourceCopy(const AnnotatedOperation& original_aop,
+                              std::vector<AnnotatedOperation>* result_aops);
+
+  // Takes a REPLACE/REPLACE_BZ operation |aop|, and adds one operation for each
+  // dst extent in |aop| to |ops|. The new operations added to |ops| will have
+  // only one dst extent each, and may be either a REPLACE or REPLACE_BZ
+  // depending on whether compression is advantageous.
+  static bool SplitReplaceOrReplaceBz(
+      const AnnotatedOperation& original_aop,
+      std::vector<AnnotatedOperation>* result_aops,
+      const std::string& target_part,
+      int data_fd,
+      off_t* data_file_size);
+
+  // Takes a sorted (by first destination extent) vector of operations |aops|
+  // and merges SOURCE_COPY, REPLACE, and REPLACE_BZ operations in that vector.
+  // It will merge two operations if:
+  //   - They are of the same type.
+  //   - They are contiguous.
+  //   - Their combined blocks do not exceed |chunk_size|.
+  static bool MergeOperations(std::vector<AnnotatedOperation>* aops,
+                              off_t chunk_size,
+                              const std::string& target_part,
+                              int data_fd,
+                              off_t* data_file_size);
+
+ private:
+  // Adds the data payload for a REPLACE/REPLACE_BZ operation |aop| by reading
+  // its output extents from |target_part_path| and appending a corresponding
+  // data blob to |data_fd|. The blob will be compressed if this is smaller than
+  // the uncompressed form, and the operation type will be set accordingly.
+  // |*data_file_size| will be updated as well. If the operation happens to have
+  // the right type and already points to a data blob, we check whether its
+  // content is identical to the new one, in which case nothing is written.
+  static bool AddDataAndSetType(AnnotatedOperation* aop,
+                                const std::string& target_part_path,
+                                int data_fd,
+                                off_t* data_file_size);
+
+  DISALLOW_COPY_AND_ASSIGN(ABGenerator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
diff --git a/payload_generator/ab_generator_unittest.cc b/payload_generator/ab_generator_unittest.cc
new file mode 100644
index 0000000..9241701
--- /dev/null
+++ b/payload_generator/ab_generator_unittest.cc
@@ -0,0 +1,558 @@
+// 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/ab_generator.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+bool ExtentEquals(Extent ext, uint64_t start_block, uint64_t num_blocks) {
+  return ext.start_block() == start_block && ext.num_blocks() == num_blocks;
+}
+
+// Tests splitting of a REPLACE/REPLACE_BZ operation.
+void TestSplitReplaceOrReplaceBzOperation(
+    DeltaArchiveManifest_InstallOperation_Type orig_type,
+    bool compressible) {
+  const size_t op_ex1_start_block = 2;
+  const size_t op_ex1_num_blocks = 2;
+  const size_t op_ex2_start_block = 6;
+  const size_t op_ex2_num_blocks = 1;
+  const size_t part_num_blocks = 7;
+
+  // Create the target partition data.
+  string part_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "SplitReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+  ScopedPathUnlinker part_path_unlinker(part_path);
+  const size_t part_size = part_num_blocks * kBlockSize;
+  chromeos::Blob part_data;
+  if (compressible) {
+    part_data.resize(part_size);
+    test_utils::FillWithData(&part_data);
+  } else {
+    std::mt19937 gen(12345);
+    std::uniform_int_distribution<uint8_t> dis(0, 255);
+    for (uint32_t i = 0; i < part_size; i++)
+      part_data.push_back(dis(gen));
+  }
+  ASSERT_EQ(part_size, part_data.size());
+  ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+  // Create original operation and blob data.
+  const size_t op_ex1_offset = op_ex1_start_block * kBlockSize;
+  const size_t op_ex1_size = op_ex1_num_blocks * kBlockSize;
+  const size_t op_ex2_offset = op_ex2_start_block * kBlockSize;
+  const size_t op_ex2_size = op_ex2_num_blocks * kBlockSize;
+  DeltaArchiveManifest_InstallOperation op;
+  op.set_type(orig_type);
+  *(op.add_dst_extents()) = ExtentForRange(op_ex1_start_block,
+                                           op_ex1_num_blocks);
+  *(op.add_dst_extents()) = ExtentForRange(op_ex2_start_block,
+                                           op_ex2_num_blocks);
+  op.set_dst_length(op_ex1_num_blocks + op_ex2_num_blocks);
+
+  chromeos::Blob op_data;
+  op_data.insert(op_data.end(),
+                 part_data.begin() + op_ex1_offset,
+                 part_data.begin() + op_ex1_offset + op_ex1_size);
+  op_data.insert(op_data.end(),
+                 part_data.begin() + op_ex2_offset,
+                 part_data.begin() + op_ex2_offset + op_ex2_size);
+  chromeos::Blob op_blob;
+  if (orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+    op_blob = op_data;
+  } else {
+    ASSERT_TRUE(BzipCompress(op_data, &op_blob));
+  }
+  op.set_data_offset(0);
+  op.set_data_length(op_blob.size());
+
+  AnnotatedOperation aop;
+  aop.op = op;
+  aop.name = "SplitTestOp";
+
+  // Create the data file.
+  string data_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "SplitReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  int data_fd = open(data_path.c_str(), O_RDWR, 000);
+  EXPECT_GE(data_fd, 0);
+  ScopedFdCloser data_fd_closer(&data_fd);
+  EXPECT_TRUE(utils::WriteFile(data_path.c_str(), op_blob.data(),
+                               op_blob.size()));
+  off_t data_file_size = op_blob.size();
+
+  // Split the operation.
+  vector<AnnotatedOperation> result_ops;
+  ASSERT_TRUE(ABGenerator::SplitReplaceOrReplaceBz(
+          aop, &result_ops, part_path, data_fd, &data_file_size));
+
+  // Check the result.
+  DeltaArchiveManifest_InstallOperation_Type expected_type =
+      compressible ?
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ :
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+
+  ASSERT_EQ(2, result_ops.size());
+
+  EXPECT_EQ("SplitTestOp:0", result_ops[0].name);
+  DeltaArchiveManifest_InstallOperation first_op = result_ops[0].op;
+  EXPECT_EQ(expected_type, first_op.type());
+  EXPECT_EQ(op_ex1_size, first_op.dst_length());
+  EXPECT_EQ(1, first_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(first_op.dst_extents(0), op_ex1_start_block,
+                           op_ex1_num_blocks));
+  // Obtain the expected blob.
+  chromeos::Blob first_expected_data(
+      part_data.begin() + op_ex1_offset,
+      part_data.begin() + op_ex1_offset + op_ex1_size);
+  chromeos::Blob first_expected_blob;
+  if (compressible) {
+    ASSERT_TRUE(BzipCompress(first_expected_data, &first_expected_blob));
+  } else {
+    first_expected_blob = first_expected_data;
+  }
+  EXPECT_EQ(first_expected_blob.size(), first_op.data_length());
+  // Check that the actual blob matches what's expected.
+  chromeos::Blob first_data_blob(first_op.data_length());
+  ssize_t bytes_read;
+  ASSERT_TRUE(utils::PReadAll(data_fd,
+                              first_data_blob.data(),
+                              first_op.data_length(),
+                              first_op.data_offset(),
+                              &bytes_read));
+  ASSERT_EQ(bytes_read, first_op.data_length());
+  EXPECT_EQ(first_expected_blob, first_data_blob);
+
+  EXPECT_EQ("SplitTestOp:1", result_ops[1].name);
+  DeltaArchiveManifest_InstallOperation second_op = result_ops[1].op;
+  EXPECT_EQ(expected_type, second_op.type());
+  EXPECT_EQ(op_ex2_size, second_op.dst_length());
+  EXPECT_EQ(1, second_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(second_op.dst_extents(0), op_ex2_start_block,
+                           op_ex2_num_blocks));
+  // Obtain the expected blob.
+  chromeos::Blob second_expected_data(
+      part_data.begin() + op_ex2_offset,
+      part_data.begin() + op_ex2_offset + op_ex2_size);
+  chromeos::Blob second_expected_blob;
+  if (compressible) {
+    ASSERT_TRUE(BzipCompress(second_expected_data, &second_expected_blob));
+  } else {
+    second_expected_blob = second_expected_data;
+  }
+  EXPECT_EQ(second_expected_blob.size(), second_op.data_length());
+  // Check that the actual blob matches what's expected.
+  chromeos::Blob second_data_blob(second_op.data_length());
+  ASSERT_TRUE(utils::PReadAll(data_fd,
+                              second_data_blob.data(),
+                              second_op.data_length(),
+                              second_op.data_offset(),
+                              &bytes_read));
+  ASSERT_EQ(bytes_read, second_op.data_length());
+  EXPECT_EQ(second_expected_blob, second_data_blob);
+
+  // Check relative layout of data blobs.
+  EXPECT_EQ(first_op.data_offset() + first_op.data_length(),
+            second_op.data_offset());
+  EXPECT_EQ(second_op.data_offset() + second_op.data_length(), data_file_size);
+  // If we split a REPLACE into multiple ones, ensure reuse of preexisting blob.
+  if (!compressible &&
+      orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+    EXPECT_EQ(0, first_op.data_offset());
+  }
+}
+
+// Tests merging of REPLACE/REPLACE_BZ operations.
+void TestMergeReplaceOrReplaceBzOperations(
+    DeltaArchiveManifest_InstallOperation_Type orig_type,
+    bool compressible) {
+  const size_t first_op_num_blocks = 1;
+  const size_t second_op_num_blocks = 2;
+  const size_t total_op_num_blocks = first_op_num_blocks + second_op_num_blocks;
+  const size_t part_num_blocks = total_op_num_blocks + 2;
+
+  // Create the target partition data.
+  string part_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "MergeReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+  ScopedPathUnlinker part_path_unlinker(part_path);
+  const size_t part_size = part_num_blocks * kBlockSize;
+  chromeos::Blob part_data;
+  if (compressible) {
+    part_data.resize(part_size);
+    test_utils::FillWithData(&part_data);
+  } else {
+    std::mt19937 gen(12345);
+    std::uniform_int_distribution<uint8_t> dis(0, 255);
+    for (uint32_t i = 0; i < part_size; i++)
+      part_data.push_back(dis(gen));
+  }
+  ASSERT_EQ(part_size, part_data.size());
+  ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+  // Create original operations and blob data.
+  vector<AnnotatedOperation> aops;
+  chromeos::Blob blob_data;
+  const size_t total_op_size = total_op_num_blocks * kBlockSize;
+
+  DeltaArchiveManifest_InstallOperation first_op;
+  first_op.set_type(orig_type);
+  const size_t first_op_size = first_op_num_blocks * kBlockSize;
+  first_op.set_dst_length(first_op_size);
+  *(first_op.add_dst_extents()) = ExtentForRange(0, first_op_num_blocks);
+  chromeos::Blob first_op_data(part_data.begin(),
+                               part_data.begin() + first_op_size);
+  chromeos::Blob first_op_blob;
+  if (orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+    first_op_blob = first_op_data;
+  } else {
+    ASSERT_TRUE(BzipCompress(first_op_data, &first_op_blob));
+  }
+  first_op.set_data_offset(0);
+  first_op.set_data_length(first_op_blob.size());
+  blob_data.insert(blob_data.end(), first_op_blob.begin(), first_op_blob.end());
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  first_aop.name = "first";
+  aops.push_back(first_aop);
+
+  DeltaArchiveManifest_InstallOperation second_op;
+  second_op.set_type(orig_type);
+  const size_t second_op_size = second_op_num_blocks * kBlockSize;
+  second_op.set_dst_length(second_op_size);
+  *(second_op.add_dst_extents()) = ExtentForRange(first_op_num_blocks,
+                                                  second_op_num_blocks);
+  chromeos::Blob second_op_data(part_data.begin() + first_op_size,
+                                part_data.begin() + total_op_size);
+  chromeos::Blob second_op_blob;
+  if (orig_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+    second_op_blob = second_op_data;
+  } else {
+    ASSERT_TRUE(BzipCompress(second_op_data, &second_op_blob));
+  }
+  second_op.set_data_offset(first_op_blob.size());
+  second_op.set_data_length(second_op_blob.size());
+  blob_data.insert(blob_data.end(), second_op_blob.begin(),
+                   second_op_blob.end());
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  second_aop.name = "second";
+  aops.push_back(second_aop);
+
+  // Create the data file.
+  string data_path;
+  EXPECT_TRUE(utils::MakeTempFile(
+      "MergeReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  int data_fd = open(data_path.c_str(), O_RDWR, 000);
+  EXPECT_GE(data_fd, 0);
+  ScopedFdCloser data_fd_closer(&data_fd);
+  EXPECT_TRUE(utils::WriteFile(data_path.c_str(), blob_data.data(),
+                               blob_data.size()));
+  off_t data_file_size = blob_data.size();
+
+  // Merge the operations.
+  EXPECT_TRUE(ABGenerator::MergeOperations(
+      &aops, 5 * kBlockSize, part_path, data_fd, &data_file_size));
+
+  // Check the result.
+  DeltaArchiveManifest_InstallOperation_Type expected_op_type =
+      compressible ?
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ :
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE;
+  EXPECT_EQ(1, aops.size());
+  DeltaArchiveManifest_InstallOperation new_op = aops[0].op;
+  EXPECT_EQ(expected_op_type, new_op.type());
+  EXPECT_FALSE(new_op.has_src_length());
+  EXPECT_EQ(total_op_num_blocks * kBlockSize, new_op.dst_length());
+  EXPECT_EQ(1, new_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(new_op.dst_extents(0), 0, total_op_num_blocks));
+  EXPECT_EQ("first,second", aops[0].name);
+
+  // Check to see if the blob pointed to in the new extent has what we expect.
+  chromeos::Blob expected_data(part_data.begin(),
+                               part_data.begin() + total_op_size);
+  chromeos::Blob expected_blob;
+  if (compressible) {
+    ASSERT_TRUE(BzipCompress(expected_data, &expected_blob));
+  } else {
+    expected_blob = expected_data;
+  }
+  ASSERT_EQ(expected_blob.size(), new_op.data_length());
+  ASSERT_EQ(blob_data.size() + expected_blob.size(), data_file_size);
+  chromeos::Blob new_op_blob(new_op.data_length());
+  ssize_t bytes_read;
+  ASSERT_TRUE(utils::PReadAll(data_fd,
+                              new_op_blob.data(),
+                              new_op.data_length(),
+                              new_op.data_offset(),
+                              &bytes_read));
+  ASSERT_EQ(new_op.data_length(), bytes_read);
+  EXPECT_EQ(expected_blob, new_op_blob);
+}
+
+}  // namespace
+
+class ABGeneratorTest : public ::testing::Test {};
+
+TEST_F(ABGeneratorTest, SplitSourceCopyTest) {
+  DeltaArchiveManifest_InstallOperation op;
+  op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+  *(op.add_src_extents()) = ExtentForRange(2, 3);
+  *(op.add_src_extents()) = ExtentForRange(6, 1);
+  *(op.add_src_extents()) = ExtentForRange(8, 4);
+  *(op.add_dst_extents()) = ExtentForRange(10, 2);
+  *(op.add_dst_extents()) = ExtentForRange(14, 3);
+  *(op.add_dst_extents()) = ExtentForRange(18, 3);
+
+  AnnotatedOperation aop;
+  aop.op = op;
+  aop.name = "SplitSourceCopyTestOp";
+  vector<AnnotatedOperation> result_ops;
+  EXPECT_TRUE(ABGenerator::SplitSourceCopy(aop, &result_ops));
+  EXPECT_EQ(result_ops.size(), 3);
+
+  EXPECT_EQ("SplitSourceCopyTestOp:0", result_ops[0].name);
+  DeltaArchiveManifest_InstallOperation first_op = result_ops[0].op;
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+            first_op.type());
+  EXPECT_EQ(kBlockSize * 2, first_op.src_length());
+  EXPECT_EQ(1, first_op.src_extents().size());
+  EXPECT_EQ(2, first_op.src_extents(0).start_block());
+  EXPECT_EQ(2, first_op.src_extents(0).num_blocks());
+  EXPECT_EQ(kBlockSize * 2, first_op.dst_length());
+  EXPECT_EQ(1, first_op.dst_extents().size());
+  EXPECT_EQ(10, first_op.dst_extents(0).start_block());
+  EXPECT_EQ(2, first_op.dst_extents(0).num_blocks());
+
+  EXPECT_EQ("SplitSourceCopyTestOp:1", result_ops[1].name);
+  DeltaArchiveManifest_InstallOperation second_op = result_ops[1].op;
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+            second_op.type());
+  EXPECT_EQ(kBlockSize * 3, second_op.src_length());
+  EXPECT_EQ(3, second_op.src_extents().size());
+  EXPECT_EQ(4, second_op.src_extents(0).start_block());
+  EXPECT_EQ(1, second_op.src_extents(0).num_blocks());
+  EXPECT_EQ(6, second_op.src_extents(1).start_block());
+  EXPECT_EQ(1, second_op.src_extents(1).num_blocks());
+  EXPECT_EQ(8, second_op.src_extents(2).start_block());
+  EXPECT_EQ(1, second_op.src_extents(2).num_blocks());
+  EXPECT_EQ(kBlockSize * 3, second_op.dst_length());
+  EXPECT_EQ(1, second_op.dst_extents().size());
+  EXPECT_EQ(14, second_op.dst_extents(0).start_block());
+  EXPECT_EQ(3, second_op.dst_extents(0).num_blocks());
+
+  EXPECT_EQ("SplitSourceCopyTestOp:2", result_ops[2].name);
+  DeltaArchiveManifest_InstallOperation third_op = result_ops[2].op;
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+            third_op.type());
+  EXPECT_EQ(kBlockSize * 3, third_op.src_length());
+  EXPECT_EQ(1, third_op.src_extents().size());
+  EXPECT_EQ(9, third_op.src_extents(0).start_block());
+  EXPECT_EQ(3, third_op.src_extents(0).num_blocks());
+  EXPECT_EQ(kBlockSize * 3, third_op.dst_length());
+  EXPECT_EQ(1, third_op.dst_extents().size());
+  EXPECT_EQ(18, third_op.dst_extents(0).start_block());
+  EXPECT_EQ(3, third_op.dst_extents(0).num_blocks());
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceTest) {
+  TestSplitReplaceOrReplaceBzOperation(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceIntoReplaceBzTest) {
+  TestSplitReplaceOrReplaceBzOperation(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzTest) {
+  TestSplitReplaceOrReplaceBzOperation(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzIntoReplaceTest) {
+  TestSplitReplaceOrReplaceBzOperation(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, SortOperationsByDestinationTest) {
+  vector<AnnotatedOperation> aops;
+  // One operation with multiple destination extents.
+  DeltaArchiveManifest_InstallOperation first_op;
+  *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+  *(first_op.add_dst_extents()) = ExtentForRange(10, 2);
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  first_aop.name = "first";
+  aops.push_back(first_aop);
+
+  // One with no destination extent. Should end up at the end of the vector.
+  DeltaArchiveManifest_InstallOperation second_op;
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  second_aop.name = "second";
+  aops.push_back(second_aop);
+
+  // One with one destination extent.
+  DeltaArchiveManifest_InstallOperation third_op;
+  *(third_op.add_dst_extents()) = ExtentForRange(3, 2);
+  AnnotatedOperation third_aop;
+  third_aop.op = third_op;
+  third_aop.name = "third";
+  aops.push_back(third_aop);
+
+  ABGenerator::SortOperationsByDestination(&aops);
+  EXPECT_EQ(aops.size(), 3);
+  EXPECT_EQ(third_aop.name, aops[0].name);
+  EXPECT_EQ(first_aop.name, aops[1].name);
+  EXPECT_EQ(second_aop.name, aops[2].name);
+}
+
+TEST_F(ABGeneratorTest, MergeSourceCopyOperationsTest) {
+  vector<AnnotatedOperation> aops;
+  DeltaArchiveManifest_InstallOperation first_op;
+  first_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+  first_op.set_src_length(kBlockSize);
+  first_op.set_dst_length(kBlockSize);
+  *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+  *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  first_aop.name = "1";
+  aops.push_back(first_aop);
+
+  DeltaArchiveManifest_InstallOperation second_op;
+  second_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+  second_op.set_src_length(3 * kBlockSize);
+  second_op.set_dst_length(3 * kBlockSize);
+  *(second_op.add_src_extents()) = ExtentForRange(2, 2);
+  *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+  *(second_op.add_dst_extents()) = ExtentForRange(7, 3);
+  *(second_op.add_dst_extents()) = ExtentForRange(11, 1);
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  second_aop.name = "2";
+  aops.push_back(second_aop);
+
+  DeltaArchiveManifest_InstallOperation third_op;
+  third_op.set_type(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+  third_op.set_src_length(kBlockSize);
+  third_op.set_dst_length(kBlockSize);
+  *(third_op.add_src_extents()) = ExtentForRange(11, 1);
+  *(third_op.add_dst_extents()) = ExtentForRange(12, 1);
+  AnnotatedOperation third_aop;
+  third_aop.op = third_op;
+  third_aop.name = "3";
+  aops.push_back(third_aop);
+
+  EXPECT_TRUE(ABGenerator::MergeOperations(&aops, 5 * kBlockSize,
+                                           "", 0, nullptr));
+
+  EXPECT_EQ(aops.size(), 1);
+  DeltaArchiveManifest_InstallOperation first_result_op = aops[0].op;
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY,
+            first_result_op.type());
+  EXPECT_EQ(kBlockSize * 5, first_result_op.src_length());
+  EXPECT_EQ(3, first_result_op.src_extents().size());
+  EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(0), 1, 3));
+  EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(1), 8, 2));
+  EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(2), 11, 1));
+  EXPECT_EQ(kBlockSize * 5, first_result_op.dst_length());
+  EXPECT_EQ(2, first_result_op.dst_extents().size());
+  EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(0), 6, 4));
+  EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(1), 11, 2));
+  EXPECT_EQ(aops[0].name, "1,2,3");
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsTest) {
+  TestMergeReplaceOrReplaceBzOperations(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsToReplaceBzTest) {
+  TestMergeReplaceOrReplaceBzOperations(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsTest) {
+  TestMergeReplaceOrReplaceBzOperations(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsToReplaceTest) {
+  TestMergeReplaceOrReplaceBzOperations(
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, NoMergeOperationsTest) {
+  // Test to make sure we don't merge operations that shouldn't be merged.
+  vector<AnnotatedOperation> aops;
+  DeltaArchiveManifest_InstallOperation first_op;
+  first_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+  *(first_op.add_dst_extents()) = ExtentForRange(0, 1);
+  first_op.set_data_length(kBlockSize);
+  AnnotatedOperation first_aop;
+  first_aop.op = first_op;
+  aops.push_back(first_aop);
+
+  // Should merge with first, except op types don't match...
+  DeltaArchiveManifest_InstallOperation second_op;
+  second_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+  *(second_op.add_dst_extents()) = ExtentForRange(1, 2);
+  second_op.set_data_length(2 * kBlockSize);
+  AnnotatedOperation second_aop;
+  second_aop.op = second_op;
+  aops.push_back(second_aop);
+
+  // Should merge with second, except it would exceed chunk size...
+  DeltaArchiveManifest_InstallOperation third_op;
+  third_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+  *(third_op.add_dst_extents()) = ExtentForRange(3, 3);
+  third_op.set_data_length(3 * kBlockSize);
+  AnnotatedOperation third_aop;
+  third_aop.op = third_op;
+  aops.push_back(third_aop);
+
+  // Should merge with third, except they aren't contiguous...
+  DeltaArchiveManifest_InstallOperation fourth_op;
+  fourth_op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+  *(fourth_op.add_dst_extents()) = ExtentForRange(7, 2);
+  fourth_op.set_data_length(2 * kBlockSize);
+  AnnotatedOperation fourth_aop;
+  fourth_aop.op = fourth_op;
+  aops.push_back(fourth_aop);
+
+  EXPECT_TRUE(ABGenerator::MergeOperations(
+      &aops, 4 * kBlockSize, "", 0, nullptr));
+
+  // No operations were merged, the number of ops is the same.
+  EXPECT_EQ(aops.size(), 4);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/annotated_operation.cc b/payload_generator/annotated_operation.cc
new file mode 100644
index 0000000..2aa79fc
--- /dev/null
+++ b/payload_generator/annotated_operation.cc
@@ -0,0 +1,81 @@
+// 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>
+
+#include "update_engine/utils.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
+
+bool AnnotatedOperation::SetOperationBlob(chromeos::Blob* blob, int data_fd,
+                                          off_t* data_file_size) {
+  TEST_AND_RETURN_FALSE(utils::PWriteAll(data_fd,
+                                         blob->data(),
+                                         blob->size(),
+                                         *data_file_size));
+  op.set_data_length(blob->size());
+  op.set_data_offset(*data_file_size);
+  *data_file_size += blob->size();
+  return true;
+}
+
+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/payload_generator/annotated_operation.h b/payload_generator/annotated_operation.h
new file mode 100644
index 0000000..bacc0ab
--- /dev/null
+++ b/payload_generator/annotated_operation.h
@@ -0,0 +1,39 @@
+// 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 <chromeos/secure_blob.h>
+#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;
+
+  // Writes |blob| to the end of |data_fd|, and updates |data_file_size| to
+  // match the new size of |data_fd|. It sets the data_offset and data_length
+  // in AnnotatedOperation to match the offset and size of |blob| in |data_fd|.
+  bool SetOperationBlob(chromeos::Blob* blob, int data_fd,
+                        off_t* data_file_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/payload_generator/cycle_breaker.cc b/payload_generator/cycle_breaker.cc
new file mode 100644
index 0000000..906502d
--- /dev/null
+++ b/payload_generator/cycle_breaker.cc
@@ -0,0 +1,199 @@
+// Copyright (c) 2012 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/cycle_breaker.h"
+
+#include <inttypes.h>
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/payload_generator/tarjan.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// This is the outer function from the original paper.
+void CycleBreaker::BreakCycles(const Graph& graph, set<Edge>* out_cut_edges) {
+  cut_edges_.clear();
+
+  // Make a copy, which we will modify by removing edges. Thus, in each
+  // iteration subgraph_ is the current subgraph or the original with
+  // vertices we desire. This variable was "A_K" in the original paper.
+  subgraph_ = graph;
+
+  // The paper calls for the "adjacency structure (i.e., graph) of
+  // strong (-ly connected) component K with least vertex in subgraph
+  // induced by {s, s + 1, ..., n}".
+  // We arbitrarily order each vertex by its index in the graph. Thus,
+  // each iteration, we are looking at the subgraph {s, s + 1, ..., n}
+  // and looking for the strongly connected component with vertex s.
+
+  TarjanAlgorithm tarjan;
+  skipped_ops_ = 0;
+
+  for (Graph::size_type i = 0; i < subgraph_.size(); i++) {
+    DeltaArchiveManifest_InstallOperation_Type op_type = graph[i].op.type();
+    if (op_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+        op_type == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+      skipped_ops_++;
+      continue;
+    }
+
+    if (i > 0) {
+      // Erase node (i - 1) from subgraph_. First, erase what it points to
+      subgraph_[i - 1].out_edges.clear();
+      // Now, erase any pointers to node (i - 1)
+      for (Graph::size_type j = i; j < subgraph_.size(); j++) {
+        subgraph_[j].out_edges.erase(i - 1);
+      }
+    }
+
+    // Calculate SCC (strongly connected component) with vertex i.
+    vector<Vertex::Index> component_indexes;
+    tarjan.Execute(i, &subgraph_, &component_indexes);
+
+    // Set subgraph edges for the components in the SCC.
+    for (vector<Vertex::Index>::iterator it = component_indexes.begin();
+         it != component_indexes.end(); ++it) {
+      subgraph_[*it].subgraph_edges.clear();
+      for (vector<Vertex::Index>::iterator jt = component_indexes.begin();
+           jt != component_indexes.end(); ++jt) {
+        // If there's a link from *it -> *jt in the graph,
+        // add a subgraph_ edge
+        if (utils::MapContainsKey(subgraph_[*it].out_edges, *jt))
+          subgraph_[*it].subgraph_edges.insert(*jt);
+      }
+    }
+
+    current_vertex_ = i;
+    blocked_.clear();
+    blocked_.resize(subgraph_.size());
+    blocked_graph_.clear();
+    blocked_graph_.resize(subgraph_.size());
+    Circuit(current_vertex_, 0);
+  }
+
+  out_cut_edges->swap(cut_edges_);
+  LOG(INFO) << "Cycle breaker skipped " << skipped_ops_ << " ops.";
+  DCHECK(stack_.empty());
+}
+
+static const size_t kMaxEdgesToConsider = 2;
+
+void CycleBreaker::HandleCircuit() {
+  stack_.push_back(current_vertex_);
+  CHECK_GE(stack_.size(),
+           static_cast<vector<Vertex::Index>::size_type>(2));
+  Edge min_edge = make_pair(stack_[0], stack_[1]);
+  uint64_t min_edge_weight = kuint64max;
+  size_t edges_considered = 0;
+  for (vector<Vertex::Index>::const_iterator it = stack_.begin();
+       it != (stack_.end() - 1); ++it) {
+    Edge edge = make_pair(*it, *(it + 1));
+    if (cut_edges_.find(edge) != cut_edges_.end()) {
+      stack_.pop_back();
+      return;
+    }
+    uint64_t edge_weight = graph_utils::EdgeWeight(subgraph_, edge);
+    if (edge_weight < min_edge_weight) {
+      min_edge_weight = edge_weight;
+      min_edge = edge;
+    }
+    edges_considered++;
+    if (edges_considered == kMaxEdgesToConsider)
+      break;
+  }
+  cut_edges_.insert(min_edge);
+  stack_.pop_back();
+}
+
+void CycleBreaker::Unblock(Vertex::Index u) {
+  blocked_[u] = false;
+
+  for (Vertex::EdgeMap::iterator it = blocked_graph_[u].out_edges.begin();
+       it != blocked_graph_[u].out_edges.end(); ) {
+    Vertex::Index w = it->first;
+    blocked_graph_[u].out_edges.erase(it++);
+    if (blocked_[w])
+      Unblock(w);
+  }
+}
+
+bool CycleBreaker::StackContainsCutEdge() const {
+  for (vector<Vertex::Index>::const_iterator it = ++stack_.begin(),
+           e = stack_.end(); it != e; ++it) {
+    Edge edge = make_pair(*(it - 1), *it);
+    if (utils::SetContainsKey(cut_edges_, edge)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool CycleBreaker::Circuit(Vertex::Index vertex, Vertex::Index depth) {
+  // "vertex" was "v" in the original paper.
+  bool found = false;  // Was "f" in the original paper.
+  stack_.push_back(vertex);
+  blocked_[vertex] = true;
+  {
+    static int counter = 0;
+    counter++;
+    if (counter == 10000) {
+      counter = 0;
+      std::string stack_str;
+      for (Vertex::Index index : stack_) {
+        stack_str += std::to_string(index);
+        stack_str += " -> ";
+      }
+      LOG(INFO) << "stack: " << stack_str;
+    }
+  }
+
+  for (Vertex::SubgraphEdgeMap::iterator w =
+           subgraph_[vertex].subgraph_edges.begin();
+       w != subgraph_[vertex].subgraph_edges.end(); ++w) {
+    if (*w == current_vertex_) {
+      // The original paper called for printing stack_ followed by
+      // current_vertex_ here, which is a cycle. Instead, we call
+      // HandleCircuit() to break it.
+      HandleCircuit();
+      found = true;
+    } else if (!blocked_[*w]) {
+      if (Circuit(*w, depth + 1)) {
+        found = true;
+        if ((depth > kMaxEdgesToConsider) || StackContainsCutEdge())
+          break;
+      }
+    }
+  }
+
+  if (found) {
+    Unblock(vertex);
+  } else {
+    for (Vertex::SubgraphEdgeMap::iterator w =
+             subgraph_[vertex].subgraph_edges.begin();
+         w != subgraph_[vertex].subgraph_edges.end(); ++w) {
+      if (blocked_graph_[*w].out_edges.find(vertex) ==
+          blocked_graph_[*w].out_edges.end()) {
+        blocked_graph_[*w].out_edges.insert(make_pair(vertex,
+                                                      EdgeProperties()));
+      }
+    }
+  }
+  CHECK_EQ(vertex, stack_.back());
+  stack_.pop_back();
+  return found;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/cycle_breaker.h b/payload_generator/cycle_breaker.h
new file mode 100644
index 0000000..420f1e2
--- /dev/null
+++ b/payload_generator/cycle_breaker.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2010 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_CYCLE_BREAKER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
+
+// This is a modified implementation of Donald B. Johnson's algorithm for
+// finding all elementary cycles (a.k.a. circuits) in a directed graph.
+// See the paper "Finding All the Elementary Circuits of a Directed Graph"
+// at http://dutta.csc.ncsu.edu/csc791_spring07/wrap/circuits_johnson.pdf
+// for reference.
+
+// Note: this version of the algorithm not only finds cycles, but breaks them.
+// It uses a simple greedy algorithm for cutting: when a cycle is discovered,
+// the edge with the least weight is cut. Longer term we may wish to do
+// something more intelligent, since the goal is (ideally) to minimize the
+// sum of the weights of all cut cycles. In practice, it's intractable
+// to consider all cycles before cutting any; there are simply too many.
+// In a sample graph representative of a typical workload, I found over
+// 5 * 10^15 cycles.
+
+#include <set>
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+class CycleBreaker {
+ public:
+  CycleBreaker() : skipped_ops_(0) {}
+  // out_cut_edges is replaced with the cut edges.
+  void BreakCycles(const Graph& graph, std::set<Edge>* out_cut_edges);
+
+  size_t skipped_ops() const { return skipped_ops_; }
+
+ private:
+  void HandleCircuit();
+  void Unblock(Vertex::Index u);
+  bool Circuit(Vertex::Index vertex, Vertex::Index depth);
+  bool StackContainsCutEdge() const;
+
+  std::vector<bool> blocked_;  // "blocked" in the paper
+  Vertex::Index current_vertex_;  // "s" in the paper
+  std::vector<Vertex::Index> stack_;  // the stack variable in the paper
+  Graph subgraph_;  // "A_K" in the paper
+  Graph blocked_graph_;  // "B" in the paper
+
+  std::set<Edge> cut_edges_;
+
+  // Number of operations skipped b/c we know they don't have any
+  // incoming edges.
+  size_t skipped_ops_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_CYCLE_BREAKER_H_
diff --git a/payload_generator/cycle_breaker_unittest.cc b/payload_generator/cycle_breaker_unittest.cc
new file mode 100644
index 0000000..b58bb08
--- /dev/null
+++ b/payload_generator/cycle_breaker_unittest.cc
@@ -0,0 +1,266 @@
+// Copyright (c) 2010 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/cycle_breaker.h"
+
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::pair;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+void SetOpForNodes(Graph* graph) {
+  for (Graph::iterator it = graph->begin(), e = graph->end(); it != e; ++it) {
+    it->op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+  }
+}
+}  // namespace
+
+class CycleBreakerTest : public ::testing::Test {};
+
+TEST(CycleBreakerTest, SimpleTest) {
+  int counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Vertex::Index n_e = counter++;
+  const Vertex::Index n_f = counter++;
+  const Vertex::Index n_g = counter++;
+  const Vertex::Index n_h = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+
+  graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+  graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties()));
+  graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties()));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  // The following cycles must be cut:
+  // A->E->B
+  // C->D->E
+  // G->H
+
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_e)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_e, n_b)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_c, n_d)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_e, n_c)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_g, n_h)) ||
+              utils::SetContainsKey(broken_edges, make_pair(n_h, n_g)));
+  EXPECT_EQ(3, broken_edges.size());
+}
+
+namespace {
+pair<Vertex::Index, EdgeProperties> EdgeWithWeight(Vertex::Index dest,
+uint64_t weight) {
+  EdgeProperties props;
+  props.extents.resize(1);
+  props.extents[0].set_num_blocks(weight);
+  return make_pair(dest, props);
+}
+}  // namespace
+
+
+// This creates a bunch of cycles like this:
+//
+//               root <------.
+//    (t)->     / | \        |
+//             V  V  V       |
+//             N  N  N       |
+//              \ | /        |
+//               VVV         |
+//                N          |
+//              / | \        |
+//             V  V  V       |
+//             N  N  N       |
+//               ...         |
+//     (s)->    \ | /        |
+//               VVV         |
+//                N          |
+//                 \_________/
+//
+// such that the original cutting algo would cut edges (s). We changed
+// the algorithm to cut cycles (t) instead, since they are closer to the
+// root, and that can massively speed up cycle cutting.
+TEST(CycleBreakerTest, AggressiveCutTest) {
+  int counter = 0;
+
+  const int kNodesPerGroup = 4;
+  const int kGroups = 33;
+
+  Graph graph(kGroups * kNodesPerGroup + 1);  // + 1 for the root node
+  SetOpForNodes(&graph);
+
+  const Vertex::Index n_root = counter++;
+
+  Vertex::Index last_hub = n_root;
+  for (int i = 0; i < kGroups; i++) {
+    uint64_t weight = 5;
+    if (i == 0)
+      weight = 2;
+    else if (i == (kGroups - 1))
+      weight = 1;
+
+    const Vertex::Index next_hub = counter++;
+
+    for (int j = 0; j < (kNodesPerGroup - 1); j++) {
+      const Vertex::Index node = counter++;
+      graph[last_hub].out_edges.insert(EdgeWithWeight(node, weight));
+      graph[node].out_edges.insert(EdgeWithWeight(next_hub, weight));
+    }
+    last_hub = next_hub;
+  }
+
+  graph[last_hub].out_edges.insert(EdgeWithWeight(n_root, 5));
+
+  EXPECT_EQ(counter, graph.size());
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  LOG(INFO) << "If this hangs for more than 1 second, the test has failed.";
+  breaker.BreakCycles(graph, &broken_edges);
+
+  set<Edge> expected_cuts;
+
+  for (Vertex::EdgeMap::const_iterator it = graph[n_root].out_edges.begin(),
+       e = graph[n_root].out_edges.end(); it != e; ++it) {
+    expected_cuts.insert(make_pair(n_root, it->first));
+  }
+
+  EXPECT_TRUE(broken_edges == expected_cuts);
+}
+
+TEST(CycleBreakerTest, WeightTest) {
+  int counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Vertex::Index n_e = counter++;
+  const Vertex::Index n_f = counter++;
+  const Vertex::Index n_g = counter++;
+  const Vertex::Index n_h = counter++;
+  const Vertex::Index n_i = counter++;
+  const Vertex::Index n_j = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 4));
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_f, 3));
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_h, 2));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_a, 3));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 4));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 5));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_d, 3));
+  graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 6));
+  graph[n_d].out_edges.insert(EdgeWithWeight(n_e, 3));
+  graph[n_e].out_edges.insert(EdgeWithWeight(n_d, 4));
+  graph[n_e].out_edges.insert(EdgeWithWeight(n_g, 5));
+  graph[n_f].out_edges.insert(EdgeWithWeight(n_g, 2));
+  graph[n_g].out_edges.insert(EdgeWithWeight(n_f, 3));
+  graph[n_g].out_edges.insert(EdgeWithWeight(n_d, 5));
+  graph[n_h].out_edges.insert(EdgeWithWeight(n_i, 8));
+  graph[n_i].out_edges.insert(EdgeWithWeight(n_e, 4));
+  graph[n_i].out_edges.insert(EdgeWithWeight(n_h, 9));
+  graph[n_i].out_edges.insert(EdgeWithWeight(n_j, 6));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  // These are required to be broken:
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_b, n_c)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_f, n_g)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_h, n_i)));
+}
+
+TEST(CycleBreakerTest, UnblockGraphTest) {
+  int counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1));
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_c, 1));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 2));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 2));
+  graph[n_b].out_edges.insert(EdgeWithWeight(n_d, 2));
+  graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 2));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  // These are required to be broken:
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_b)));
+  EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_c)));
+}
+
+TEST(CycleBreakerTest, SkipOpsTest) {
+  int counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+  SetOpForNodes(&graph);
+  graph[n_a].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+  graph[n_c].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+
+  graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1));
+  graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 1));
+
+  CycleBreaker breaker;
+
+  set<Edge> broken_edges;
+  breaker.BreakCycles(graph, &broken_edges);
+
+  EXPECT_EQ(2, breaker.skipped_ops());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc
new file mode 100644
index 0000000..dbb63a8
--- /dev/null
+++ b/payload_generator/delta_diff_generator.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2012 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/delta_diff_generator.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/ab_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/full_update_generator.h"
+#include "update_engine/payload_generator/inplace_generator.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// bytes
+const size_t kRootFSPartitionSize = static_cast<size_t>(2) * 1024 * 1024 * 1024;
+const size_t kBlockSize = 4096;  // bytes
+
+bool GenerateUpdatePayloadFile(
+    const PayloadGenerationConfig& config,
+    const string& output_path,
+    const string& private_key_path,
+    uint64_t* metadata_size) {
+  if (config.is_delta) {
+    LOG_IF(WARNING, config.source.rootfs.size != config.target.rootfs.size)
+        << "Old and new images have different block counts.";
+    // TODO(deymo): Our tools only support growing the filesystem size during
+    // an update. Remove this check when that's fixed. crbug.com/192136
+    LOG_IF(FATAL, config.source.rootfs.size > config.target.rootfs.size)
+        << "Shirking the rootfs size is not supported at the moment.";
+  }
+
+  // Sanity checks for the partition size.
+  LOG(INFO) << "Rootfs partition size: " << config.rootfs_partition_size;
+  LOG(INFO) << "Actual filesystem size: " << config.target.rootfs.size;
+
+  LOG(INFO) << "Block count: "
+            << config.target.rootfs.size / config.block_size;
+
+  LOG_IF(INFO, config.source.kernel.path.empty())
+      << "Will generate full kernel update.";
+
+  const string kTempFileTemplate("CrAU_temp_data.XXXXXX");
+  string temp_file_path;
+  off_t data_file_size = 0;
+
+  LOG(INFO) << "Reading files...";
+
+  // Create empty payload file object.
+  PayloadFile payload;
+  TEST_AND_RETURN_FALSE(payload.Init(config));
+
+  vector<AnnotatedOperation> rootfs_ops;
+  vector<AnnotatedOperation> kernel_ops;
+
+  // Select payload generation strategy based on the config.
+  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.path)) {
+      LOG(INFO) << "Using generator FullUpdateGenerator() for squashfs deltas";
+      strategy.reset(new FullUpdateGenerator());
+    } else if (utils::IsExtFilesystem(config.target.rootfs.path)) {
+      // Delta update (with possibly a full kernel update).
+      if (config.minor_version == kInPlaceMinorPayloadVersion) {
+        LOG(INFO) << "Using generator InplaceGenerator()";
+        strategy.reset(new InplaceGenerator());
+      } else if (config.minor_version == kSourceMinorPayloadVersion) {
+        LOG(INFO) << "Using generator ABGenerator()";
+        strategy.reset(new ABGenerator());
+      } else {
+        LOG(ERROR) << "Unsupported minor version given for delta payload: "
+                   << config.minor_version;
+        return false;
+      }
+    } else {
+      LOG(ERROR) << "Unsupported filesystem for delta payload in "
+                 << config.target.rootfs.path;
+      return false;
+    }
+  } else {
+    // Full update.
+    LOG(INFO) << "Using generator FullUpdateGenerator()";
+    strategy.reset(new FullUpdateGenerator());
+  }
+
+  int data_file_fd;
+  TEST_AND_RETURN_FALSE(
+      utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd));
+  unique_ptr<ScopedPathUnlinker> temp_file_unlinker(
+      new ScopedPathUnlinker(temp_file_path));
+  TEST_AND_RETURN_FALSE(data_file_fd >= 0);
+
+  {
+    ScopedFdCloser data_file_fd_closer(&data_file_fd);
+    // Generate the operations using the strategy we selected above.
+    TEST_AND_RETURN_FALSE(strategy->GenerateOperations(config,
+                                                       data_file_fd,
+                                                       &data_file_size,
+                                                       &rootfs_ops,
+                                                       &kernel_ops));
+  }
+
+  // Filter the no-operations. OperationsGenerators should not output this kind
+  // of operations normally, but this is an extra step to fix that if
+  // happened.
+  diff_utils::FilterNoopOperations(&rootfs_ops);
+  diff_utils::FilterNoopOperations(&kernel_ops);
+
+  // Write payload file to disk.
+  payload.AddPartitionOperations(PartitionName::kRootfs, rootfs_ops);
+  payload.AddPartitionOperations(PartitionName::kKernel, kernel_ops);
+  payload.WritePayload(output_path, temp_file_path, private_key_path,
+                       metadata_size);
+  temp_file_unlinker.reset();
+
+  LOG(INFO) << "All done. Successfully created delta file with "
+            << "metadata size = " << *metadata_size;
+  return true;
+}
+
+};  // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_generator.h b/payload_generator/delta_diff_generator.h
new file mode 100644
index 0000000..405f451
--- /dev/null
+++ b/payload_generator/delta_diff_generator.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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_DELTA_DIFF_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+
+#include <string>
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+extern const size_t kBlockSize;
+extern const size_t kRootFSPartitionSize;
+
+// 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/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
new file mode 100644
index 0000000..6c15919
--- /dev/null
+++ b/payload_generator/delta_diff_utils.cc
@@ -0,0 +1,517 @@
+// 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/delta_diff_utils.h"
+
+#include <algorithm>
+#include <map>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+using std::map;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+namespace {
+
+const char* const kBsdiffPath = "bsdiff";
+
+// The maximum destination size allowed for bsdiff. In general, bsdiff should
+// work for arbitrary big files, but the payload generation and payload
+// application requires a significant amount of RAM. We put a hard-limit of
+// 200 MiB that should not affect any released board, but will limit the
+// Chrome binary in ASan builders.
+const uint64_t kMaxBsdiffDestinationSize = 200 * 1024 * 1024;  // bytes
+
+// Process a range of blocks from |range_start| to |range_end| in the extent at
+// position |*idx_p| of |extents|. If |do_remove| is true, this range will be
+// removed, which may cause the extent to be trimmed, split or removed entirely.
+// The value of |*idx_p| is updated to point to the next extent to be processed.
+// Returns true iff the next extent to process is a new or updated one.
+bool ProcessExtentBlockRange(vector<Extent>* extents, size_t* idx_p,
+                             const bool do_remove, uint64_t range_start,
+                             uint64_t range_end) {
+  size_t idx = *idx_p;
+  uint64_t start_block = (*extents)[idx].start_block();
+  uint64_t num_blocks = (*extents)[idx].num_blocks();
+  uint64_t range_size = range_end - range_start;
+
+  if (do_remove) {
+    if (range_size == num_blocks) {
+      // Remove the entire extent.
+      extents->erase(extents->begin() + idx);
+    } else if (range_end == num_blocks) {
+      // Trim the end of the extent.
+      (*extents)[idx].set_num_blocks(num_blocks - range_size);
+      idx++;
+    } else if (range_start == 0) {
+      // Trim the head of the extent.
+      (*extents)[idx].set_start_block(start_block + range_size);
+      (*extents)[idx].set_num_blocks(num_blocks - range_size);
+    } else {
+      // Trim the middle, splitting the remainder into two parts.
+      (*extents)[idx].set_num_blocks(range_start);
+      Extent e;
+      e.set_start_block(start_block + range_end);
+      e.set_num_blocks(num_blocks - range_end);
+      idx++;
+      extents->insert(extents->begin() + idx, e);
+    }
+  } else if (range_end == num_blocks) {
+    // Done with this extent.
+    idx++;
+  } else {
+    return false;
+  }
+
+  *idx_p = idx;
+  return true;
+}
+
+// Remove identical corresponding block ranges in |src_extents| and
+// |dst_extents|. Used for preventing moving of blocks onto themselves during
+// MOVE operations. The value of |total_bytes| indicates the actual length of
+// content; this may be slightly less than the total size of blocks, in which
+// case the last block is only partly occupied with data. Returns the total
+// number of bytes removed.
+size_t RemoveIdenticalBlockRanges(vector<Extent>* src_extents,
+                                  vector<Extent>* dst_extents,
+                                  const size_t total_bytes) {
+  size_t src_idx = 0;
+  size_t dst_idx = 0;
+  uint64_t src_offset = 0, dst_offset = 0;
+  bool new_src = true, new_dst = true;
+  size_t removed_bytes = 0, nonfull_block_bytes;
+  bool do_remove = false;
+  while (src_idx < src_extents->size() && dst_idx < dst_extents->size()) {
+    if (new_src) {
+      src_offset = 0;
+      new_src = false;
+    }
+    if (new_dst) {
+      dst_offset = 0;
+      new_dst = false;
+    }
+
+    do_remove = ((*src_extents)[src_idx].start_block() + src_offset ==
+                 (*dst_extents)[dst_idx].start_block() + dst_offset);
+
+    uint64_t src_num_blocks = (*src_extents)[src_idx].num_blocks();
+    uint64_t dst_num_blocks = (*dst_extents)[dst_idx].num_blocks();
+    uint64_t min_num_blocks = std::min(src_num_blocks - src_offset,
+                                       dst_num_blocks - dst_offset);
+    uint64_t prev_src_offset = src_offset;
+    uint64_t prev_dst_offset = dst_offset;
+    src_offset += min_num_blocks;
+    dst_offset += min_num_blocks;
+
+    new_src = ProcessExtentBlockRange(src_extents, &src_idx, do_remove,
+                                      prev_src_offset, src_offset);
+    new_dst = ProcessExtentBlockRange(dst_extents, &dst_idx, do_remove,
+                                      prev_dst_offset, dst_offset);
+    if (do_remove)
+      removed_bytes += min_num_blocks * kBlockSize;
+  }
+
+  // If we removed the last block and this block is only partly used by file
+  // content, deduct the unused portion from the total removed byte count.
+  if (do_remove && (nonfull_block_bytes = total_bytes % kBlockSize))
+    removed_bytes -= kBlockSize - nonfull_block_bytes;
+
+  return removed_bytes;
+}
+
+}  // namespace
+
+namespace diff_utils {
+
+bool DeltaReadPartition(
+    vector<AnnotatedOperation>* aops,
+    const PartitionConfig& old_part,
+    const PartitionConfig& new_part,
+    off_t chunk_blocks,
+    int data_fd,
+    off_t* data_file_size,
+    bool skip_block_0,
+    bool src_ops_allowed) {
+  ExtentRanges old_visited_blocks;
+  ExtentRanges new_visited_blocks;
+
+  // We can't produce a MOVE operation with a 0 block as neither source nor
+  // destination, so we avoid generating an operation for the block 0 here, and
+  // we will add an operation for it in the InplaceGenerator. Excluding both
+  // old and new blocks ensures that identical images would still produce empty
+  // deltas.
+  if (skip_block_0) {
+    old_visited_blocks.AddBlock(0);
+    new_visited_blocks.AddBlock(0);
+  }
+
+  map<string, vector<Extent>> old_files_map;
+  if (old_part.fs_interface) {
+    vector<FilesystemInterface::File> old_files;
+    old_part.fs_interface->GetFiles(&old_files);
+    for (const FilesystemInterface::File& file : old_files)
+      old_files_map[file.name] = file.extents;
+  }
+
+  TEST_AND_RETURN_FALSE(new_part.fs_interface);
+  vector<FilesystemInterface::File> new_files;
+  new_part.fs_interface->GetFiles(&new_files);
+
+  // The processing is very straightforward here, we generate operations for
+  // every file (and pseudo-file such as the metadata) in the new filesystem
+  // based on the file with the same name in the old filesystem, if any.
+  // Files with overlapping data blocks (like hardlinks or filesystems with tail
+  // packing or compression where the blocks store more than one file) are only
+  // generated once in the new image, but are also used only once from the old
+  // image due to some simplifications (see below).
+  for (const FilesystemInterface::File& new_file : new_files) {
+    // Ignore the files in the new filesystem without blocks. Symlinks with
+    // data blocks (for example, symlinks bigger than 60 bytes in ext2) are
+    // handled as normal files. We also ignore blocks that were already
+    // processed by a previous file.
+    vector<Extent> new_file_extents = FilterExtentRanges(
+        new_file.extents, new_visited_blocks);
+    new_visited_blocks.AddExtents(new_file_extents);
+
+    if (new_file_extents.empty())
+      continue;
+
+    LOG(INFO) << "Encoding file " << new_file.name << " ("
+              << BlocksInExtents(new_file_extents) << " blocks)";
+
+    // We can't visit each dst image inode more than once, as that would
+    // duplicate work. Here, we avoid visiting each source image inode
+    // more than once. Technically, we could have multiple operations
+    // that read the same blocks from the source image for diffing, but
+    // we choose not to avoid complexity. Eventually we will move away
+    // from using a graph/cycle detection/etc to generate diffs, and at that
+    // time, it will be easy (non-complex) to have many operations read
+    // from the same source blocks. At that time, this code can die. -adlr
+    vector<Extent> old_file_extents = FilterExtentRanges(
+        old_files_map[new_file.name], old_visited_blocks);
+    old_visited_blocks.AddExtents(old_file_extents);
+
+    TEST_AND_RETURN_FALSE(DeltaReadFile(
+        aops,
+        old_part.path,
+        new_part.path,
+        old_file_extents,
+        new_file_extents,
+        new_file.name,  // operation name
+        chunk_blocks,
+        data_fd,
+        data_file_size,
+        src_ops_allowed));
+  }
+  // Process all the blocks not included in any file. We provided all the unused
+  // blocks in the old partition as available data.
+  vector<Extent> new_unvisited = {
+      ExtentForRange(0, new_part.size / kBlockSize)};
+  new_unvisited = FilterExtentRanges(new_unvisited, new_visited_blocks);
+  if (new_unvisited.empty())
+    return true;
+
+  vector<Extent> old_unvisited;
+  if (old_part.fs_interface) {
+    old_unvisited.push_back(ExtentForRange(0, old_part.size / kBlockSize));
+    old_unvisited = FilterExtentRanges(old_unvisited, old_visited_blocks);
+  }
+
+  LOG(INFO) << "Scanning " << BlocksInExtents(new_unvisited)
+            << " unwritten blocks";
+  TEST_AND_RETURN_FALSE(DeltaReadFile(
+      aops,
+      old_part.path,
+      new_part.path,
+      old_unvisited,
+      new_unvisited,
+      "<non-file-data>",  // operation name
+      chunk_blocks,
+      data_fd,
+      data_file_size,
+      src_ops_allowed));
+
+  return true;
+}
+
+bool DeltaReadFile(
+    vector<AnnotatedOperation>* aops,
+    const string& old_part,
+    const string& new_part,
+    const vector<Extent>& old_extents,
+    const vector<Extent>& new_extents,
+    const string& name,
+    off_t chunk_blocks,
+    int data_fd,
+    off_t* data_file_size,
+    bool src_ops_allowed) {
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation operation;
+
+  uint64_t total_blocks = BlocksInExtents(new_extents);
+  if (chunk_blocks == -1)
+    chunk_blocks = total_blocks;
+
+  // If bsdiff breaks again, blacklist the problem file by using:
+  //   bsdiff_allowed = (name != "/foo/bar")
+  //
+  // TODO(dgarrett): chromium-os:15274 connect this test to the command line.
+  bool bsdiff_allowed = true;
+  if (static_cast<uint64_t>(chunk_blocks) * kBlockSize >
+      kMaxBsdiffDestinationSize) {
+    bsdiff_allowed = false;
+  }
+
+  if (!bsdiff_allowed) {
+    LOG(INFO) << "bsdiff blacklisting: " << name;
+  }
+
+  for (uint64_t block_offset = 0; block_offset < total_blocks;
+      block_offset += chunk_blocks) {
+    // Split the old/new file in the same chunks. Note that this could drop
+    // some information from the old file used for the new chunk. If the old
+    // file is smaller (or even empty when there's no old file) the chunk will
+    // also be empty.
+    vector<Extent> old_extents_chunk = ExtentsSublist(
+        old_extents, block_offset, chunk_blocks);
+    vector<Extent> new_extents_chunk = ExtentsSublist(
+        new_extents, block_offset, chunk_blocks);
+    NormalizeExtents(&old_extents_chunk);
+    NormalizeExtents(&new_extents_chunk);
+
+    TEST_AND_RETURN_FALSE(ReadExtentsToDiff(old_part,
+                                            new_part,
+                                            old_extents_chunk,
+                                            new_extents_chunk,
+                                            bsdiff_allowed,
+                                            &data,
+                                            &operation,
+                                            src_ops_allowed));
+
+    // Check if the operation writes nothing.
+    if (operation.dst_extents_size() == 0) {
+      if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE) {
+        LOG(INFO) << "Empty MOVE operation ("
+                  << name << "), skipping";
+        continue;
+      } else {
+        LOG(ERROR) << "Empty non-MOVE operation";
+        return false;
+      }
+    }
+
+    // Write the data
+    if (operation.type() != DeltaArchiveManifest_InstallOperation_Type_MOVE &&
+        operation.type() !=
+            DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY) {
+      operation.set_data_offset(*data_file_size);
+      operation.set_data_length(data.size());
+    }
+
+    TEST_AND_RETURN_FALSE(utils::WriteAll(data_fd, data.data(), data.size()));
+    *data_file_size += data.size();
+
+    // Now, insert into the list of operations.
+    AnnotatedOperation aop;
+    aop.name = name;
+    if (static_cast<uint64_t>(chunk_blocks) < total_blocks) {
+      aop.name = base::StringPrintf("%s:%" PRIu64,
+                                    name.c_str(), block_offset / chunk_blocks);
+    }
+    aop.op = operation;
+    aops->emplace_back(aop);
+  }
+  return true;
+}
+
+bool ReadExtentsToDiff(const string& old_part,
+                       const string& new_part,
+                       const vector<Extent>& old_extents,
+                       const vector<Extent>& new_extents,
+                       bool bsdiff_allowed,
+                       chromeos::Blob* out_data,
+                       DeltaArchiveManifest_InstallOperation* out_op,
+                       bool src_ops_allowed) {
+  DeltaArchiveManifest_InstallOperation operation;
+  // Data blob that will be written to delta file.
+  const chromeos::Blob* data_blob = nullptr;
+
+  // We read blocks from old_extents and write blocks to new_extents.
+  uint64_t blocks_to_read = BlocksInExtents(old_extents);
+  uint64_t blocks_to_write = BlocksInExtents(new_extents);
+
+  // Make copies of the extents so we can modify them.
+  vector<Extent> src_extents = old_extents;
+  vector<Extent> dst_extents = new_extents;
+
+  // Read in bytes from new data.
+  chromeos::Blob new_data;
+  TEST_AND_RETURN_FALSE(utils::ReadExtents(new_part,
+                                           new_extents,
+                                           &new_data,
+                                           kBlockSize * blocks_to_write,
+                                           kBlockSize));
+  TEST_AND_RETURN_FALSE(!new_data.empty());
+
+
+  // Using a REPLACE is always an option.
+  operation.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+  data_blob = &new_data;
+
+  // Try compressing it with bzip2.
+  chromeos::Blob new_data_bz;
+  TEST_AND_RETURN_FALSE(BzipCompress(new_data, &new_data_bz));
+  CHECK(!new_data_bz.empty());
+  if (new_data_bz.size() < data_blob->size()) {
+    // A REPLACE_BZ is better.
+    operation.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+    data_blob = &new_data_bz;
+  }
+
+  chromeos::Blob old_data;
+  chromeos::Blob empty_blob;
+  chromeos::Blob bsdiff_delta;
+  if (blocks_to_read > 0) {
+    // Read old data.
+    TEST_AND_RETURN_FALSE(
+        utils::ReadExtents(old_part, src_extents, &old_data,
+                           kBlockSize * blocks_to_read, kBlockSize));
+    if (old_data == new_data) {
+      // No change in data.
+      if (src_ops_allowed) {
+        operation.set_type(
+            DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY);
+      } else {
+        operation.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+      }
+      data_blob = &empty_blob;
+    } else if (bsdiff_allowed) {
+      // If the source file is considered bsdiff safe (no bsdiff bugs
+      // triggered), see if BSDIFF encoding is smaller.
+      base::FilePath old_chunk;
+      TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&old_chunk));
+      ScopedPathUnlinker old_unlinker(old_chunk.value());
+      TEST_AND_RETURN_FALSE(
+          utils::WriteFile(old_chunk.value().c_str(),
+                           old_data.data(), old_data.size()));
+      base::FilePath new_chunk;
+      TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&new_chunk));
+      ScopedPathUnlinker new_unlinker(new_chunk.value());
+      TEST_AND_RETURN_FALSE(
+          utils::WriteFile(new_chunk.value().c_str(),
+                           new_data.data(), new_data.size()));
+
+      TEST_AND_RETURN_FALSE(
+          BsdiffFiles(old_chunk.value(), new_chunk.value(), &bsdiff_delta));
+      CHECK_GT(bsdiff_delta.size(), static_cast<chromeos::Blob::size_type>(0));
+      if (bsdiff_delta.size() < data_blob->size()) {
+        if (src_ops_allowed) {
+          operation.set_type(
+              DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF);
+        } else {
+          operation.set_type(DeltaArchiveManifest_InstallOperation_Type_BSDIFF);
+        }
+        data_blob = &bsdiff_delta;
+      }
+    }
+  }
+
+  size_t removed_bytes = 0;
+  // Remove identical src/dst block ranges in MOVE operations.
+  if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE) {
+    removed_bytes = RemoveIdenticalBlockRanges(
+        &src_extents, &dst_extents, new_data.size());
+  }
+  // Set legacy src_length and dst_length fields.
+  operation.set_src_length(old_data.size() - removed_bytes);
+  operation.set_dst_length(new_data.size() - removed_bytes);
+
+  // Embed extents in the operation.
+  StoreExtents(src_extents, operation.mutable_src_extents());
+  StoreExtents(dst_extents, operation.mutable_dst_extents());
+
+  // Replace operations should not have source extents.
+  if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+      operation.type() ==
+          DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+    operation.clear_src_extents();
+    operation.clear_src_length();
+  }
+
+  *out_data = std::move(*data_blob);
+  *out_op = operation;
+
+  return true;
+}
+
+// Runs the bsdiff tool on two files and returns the resulting delta in
+// 'out'. Returns true on success.
+bool BsdiffFiles(const string& old_file,
+                 const string& new_file,
+                 chromeos::Blob* out) {
+  const string kPatchFile = "delta.patchXXXXXX";
+  string patch_file_path;
+
+  TEST_AND_RETURN_FALSE(
+      utils::MakeTempFile(kPatchFile, &patch_file_path, nullptr));
+
+  vector<string> cmd;
+  cmd.push_back(kBsdiffPath);
+  cmd.push_back(old_file);
+  cmd.push_back(new_file);
+  cmd.push_back(patch_file_path);
+
+  int rc = 1;
+  chromeos::Blob patch_file;
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc, nullptr));
+  TEST_AND_RETURN_FALSE(rc == 0);
+  TEST_AND_RETURN_FALSE(utils::ReadFile(patch_file_path, out));
+  unlink(patch_file_path.c_str());
+  return true;
+}
+
+// Returns true if |op| is a no-op operation that doesn't do any useful work
+// (e.g., a move operation that copies blocks onto themselves).
+bool IsNoopOperation(const DeltaArchiveManifest_InstallOperation& op) {
+  return (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE &&
+          ExpandExtents(op.src_extents()) == ExpandExtents(op.dst_extents()));
+}
+
+void FilterNoopOperations(vector<AnnotatedOperation>* ops) {
+  ops->erase(
+      std::remove_if(
+          ops->begin(), ops->end(),
+          [](const AnnotatedOperation& aop){return IsNoopOperation(aop.op);}),
+      ops->end());
+}
+
+bool InitializePartitionInfo(const PartitionConfig& part, PartitionInfo* info) {
+  info->set_size(part.size);
+  OmahaHashCalculator hasher;
+  TEST_AND_RETURN_FALSE(hasher.UpdateFile(part.path, part.size) ==
+                        static_cast<off_t>(part.size));
+  TEST_AND_RETURN_FALSE(hasher.Finalize());
+  const chromeos::Blob& hash = hasher.raw_hash();
+  info->set_hash(hash.data(), hash.size());
+  LOG(INFO) << part.path << ": size=" << part.size << " hash=" << hasher.hash();
+  return true;
+}
+
+}  // namespace diff_utils
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_utils.h b/payload_generator/delta_diff_utils.h
new file mode 100644
index 0000000..0be8666
--- /dev/null
+++ b/payload_generator/delta_diff_utils.h
@@ -0,0 +1,94 @@
+// 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_DELTA_DIFF_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+namespace diff_utils {
+
+// Create operations in |aops| to produce all the blocks in the |new_part|
+// partition using the filesystem opened in that PartitionConfig.
+// It uses the files reported by the filesystem in |old_part| and the data
+// blocks in that partition (if available) to determine the best way to compress
+// the new files (REPLACE, REPLACE_BZ, COPY, BSDIFF) and writes any necessary
+// data to the end of |data_fd| updating |data_file_size| accordingly.
+bool DeltaReadPartition(std::vector<AnnotatedOperation>* aops,
+                        const PartitionConfig& old_part,
+                        const PartitionConfig& new_part,
+                        off_t chunk_blocks,
+                        int data_fd,
+                        off_t* data_file_size,
+                        bool skip_block_0,
+                        bool src_ops_allowed);
+
+// For a given file |name| append operations to |aops| to produce it in the
+// |new_part|. The file will be split in chunks of |chunk_blocks| blocks each
+// or treated as a single chunk if |chunk_blocks| is -1. The file data is
+// stored in |new_part| in the blocks described by |new_extents| and, if it
+// exists, the old version exists in |old_part| in the blocks described by
+// |old_extents|. The operations added to |aops| reference the data blob
+// in the file |data_fd|, which has length *data_file_size. *data_file_size is
+// updated appropriately. Returns true on success.
+bool DeltaReadFile(std::vector<AnnotatedOperation>* aops,
+                   const std::string& old_part,
+                   const std::string& new_part,
+                   const std::vector<Extent>& old_extents,
+                   const std::vector<Extent>& new_extents,
+                   const std::string& name,
+                   off_t chunk_blocks,
+                   int data_fd,
+                   off_t* data_file_size,
+                   bool src_ops_allowed);
+
+// Reads the blocks |old_extents| from |old_part| (if it exists) and the
+// |new_extents| from |new_part| and determines the smallest way to encode
+// this |new_extents| for the diff. It stores necessary data in |out_data| and
+// fills in |out_op|. If there's no change in old and new files, it creates a
+// MOVE operation. If there is a change, the smallest of REPLACE, REPLACE_BZ,
+// or BSDIFF wins. |new_extents| must not be empty.
+// If |src_ops_allowed| is true, it will emit SOURCE_COPY and SOURCE_BSDIFF
+// operations instead of MOVE and BSDIFF, respectively.
+// Returns true on success.
+bool ReadExtentsToDiff(const std::string& old_part,
+                       const std::string& new_part,
+                       const std::vector<Extent>& old_extents,
+                       const std::vector<Extent>& new_extents,
+                       bool bsdiff_allowed,
+                       chromeos::Blob* out_data,
+                       DeltaArchiveManifest_InstallOperation* out_op,
+                       bool src_ops_allowed);
+
+// Runs the bsdiff tool on two files and returns the resulting delta in
+// |out|. Returns true on success.
+bool BsdiffFiles(const std::string& old_file,
+                 const std::string& new_file,
+                 chromeos::Blob* out);
+
+// Returns true if |op| is a no-op operation that doesn't do any useful work
+// (e.g., a move operation that copies blocks onto themselves).
+bool IsNoopOperation(const DeltaArchiveManifest_InstallOperation& op);
+
+// Filters all the operations that are no-op, maintaining the relative order
+// of the rest of the operations.
+void FilterNoopOperations(std::vector<AnnotatedOperation>* ops);
+
+bool InitializePartitionInfo(const PartitionConfig& partition,
+                             PartitionInfo* info);
+
+}  // namespace diff_utils
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
diff --git a/payload_generator/delta_diff_utils_unittest.cc b/payload_generator/delta_diff_utils_unittest.cc
new file mode 100644
index 0000000..2157aa1
--- /dev/null
+++ b/payload_generator/delta_diff_utils_unittest.cc
@@ -0,0 +1,479 @@
+// 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/delta_diff_utils.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/scoped_file.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/fake_filesystem.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Writes the |data| in the blocks specified by |extents| on the partition
+// |part_path|. The |data| size could be smaller than the size of the blocks
+// passed.
+bool WriteExtents(const string& part_path,
+                  const vector<Extent>& extents,
+                  off_t block_size,
+                  const chromeos::Blob& data) {
+  uint64_t offset = 0;
+  base::ScopedFILE fp(fopen(part_path.c_str(), "r+"));
+  TEST_AND_RETURN_FALSE(fp.get());
+
+  for (const Extent& extent : extents) {
+    if (offset >= data.size())
+      break;
+    TEST_AND_RETURN_FALSE(
+        fseek(fp.get(), extent.start_block() * block_size, SEEK_SET) == 0);
+    uint64_t to_write = std::min(extent.num_blocks() * block_size,
+                                 data.size() - offset);
+    TEST_AND_RETURN_FALSE(
+        fwrite(data.data() + offset, 1, to_write, fp.get()) == to_write);
+    offset += extent.num_blocks() * block_size;
+  }
+  return true;
+}
+
+}  // namespace
+
+class DeltaDiffUtilsTest : public ::testing::Test {
+ protected:
+  const uint64_t kFilesystemSize = kBlockSize * 1024;
+
+  void SetUp() override {
+    old_part_path_ = "DeltaDiffUtilsTest-old_part_path-XXXXXX";
+    CreateFilesystem(&old_fs_, &old_part_path_, kFilesystemSize);
+
+    new_part_path_ = "DeltaDiffUtilsTest-new_part_path-XXXXXX";
+    CreateFilesystem(&old_fs_, &new_part_path_, kFilesystemSize);
+  }
+
+  void TearDown() override {
+    unlink(old_part_path_.c_str());
+    unlink(new_part_path_.c_str());
+  }
+
+  // Create a fake filesystem of the given size and initialize the partition
+  // holding it.
+  void CreateFilesystem(unique_ptr<FakeFilesystem>* fs, string* filename,
+                        uint64_t size) {
+    string pattern = *filename;
+    ASSERT_TRUE(utils::MakeTempFile(pattern.c_str(), filename, nullptr));
+    ASSERT_EQ(0, truncate(filename->c_str(), size));
+    fs->reset(new FakeFilesystem(kBlockSize, size / kBlockSize));
+  }
+
+  // Paths to old and new temporary filesystems used in the tests.
+  string old_part_path_;
+  string new_part_path_;
+
+  // FilesystemInterface fake implementations used to mock out the file/block
+  // distribution.
+  unique_ptr<FakeFilesystem> old_fs_;
+  unique_ptr<FakeFilesystem> new_fs_;
+};
+
+TEST_F(DeltaDiffUtilsTest, MoveSmallTest) {
+  chromeos::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(11, 1) };
+  vector<Extent> new_extents = { ExtentForRange(1, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+  EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_path_,
+      new_part_path_,
+      old_extents,
+      new_extents,
+      true,  // bsdiff_allowed
+      &data,
+      &op,
+      false));  // src_ops_allowed
+  EXPECT_TRUE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type());
+  EXPECT_FALSE(op.has_data_offset());
+  EXPECT_FALSE(op.has_data_length());
+  EXPECT_EQ(1, op.src_extents_size());
+  EXPECT_EQ(kBlockSize, op.src_length());
+  EXPECT_EQ(1, op.dst_extents_size());
+  EXPECT_EQ(kBlockSize, op.dst_length());
+  EXPECT_EQ(BlocksInExtents(op.src_extents()),
+            BlocksInExtents(op.dst_extents()));
+  EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+}
+
+TEST_F(DeltaDiffUtilsTest, MoveWithSameBlock) {
+  // Setup the old/new files so that it has immobile chunks; we make sure to
+  // utilize all sub-cases of such chunks: blocks 21--22 induce a split (src)
+  // and complete removal (dst), whereas blocks 24--25 induce trimming of the
+  // tail (src) and head (dst) of extents. The final block (29) is used for
+  // ensuring we properly account for the number of bytes removed in cases where
+  // the last block is partly filled. The detailed configuration:
+  //
+  // Old:  [ 20     21 22     23     24 25 ] [ 28     29 ]
+  // New:  [ 18 ] [ 21 22 ] [ 20 ] [ 24 25     26 ] [ 29 ]
+  // Same:          ^^ ^^            ^^ ^^            ^^
+  vector<Extent> old_extents = {
+      ExtentForRange(20, 6),
+      ExtentForRange(28, 2) };
+  vector<Extent> new_extents = {
+      ExtentForRange(18, 1),
+      ExtentForRange(21, 2),
+      ExtentForRange(20, 1),
+      ExtentForRange(24, 3),
+      ExtentForRange(29, 1) };
+
+  uint64_t num_blocks = BlocksInExtents(old_extents);
+  EXPECT_EQ(num_blocks, BlocksInExtents(new_extents));
+
+  // The size of the data should match the total number of blocks. Each block
+  // has a different content.
+  chromeos::Blob file_data;
+  for (uint64_t i = 0; i < num_blocks; ++i) {
+    file_data.resize(file_data.size() + kBlockSize, 'a' + i);
+  }
+
+  EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, file_data));
+  EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, file_data));
+
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_path_,
+      new_part_path_,
+      old_extents,
+      new_extents,
+      true,  // bsdiff_allowed
+      &data,
+      &op,
+      false));  // src_ops_allowed
+
+  EXPECT_TRUE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type());
+  EXPECT_FALSE(op.has_data_offset());
+  EXPECT_FALSE(op.has_data_length());
+
+  // The expected old and new extents that actually moved. See comment above.
+  old_extents = {
+      ExtentForRange(20, 1),
+      ExtentForRange(23, 1),
+      ExtentForRange(28, 1) };
+  new_extents = {
+      ExtentForRange(18, 1),
+      ExtentForRange(20, 1),
+      ExtentForRange(26, 1) };
+  num_blocks = BlocksInExtents(old_extents);
+
+  EXPECT_EQ(num_blocks * kBlockSize, op.src_length());
+  EXPECT_EQ(num_blocks * kBlockSize, op.dst_length());
+
+  EXPECT_EQ(old_extents.size(), op.src_extents_size());
+  for (int i = 0; i < op.src_extents_size(); i++) {
+    EXPECT_EQ(old_extents[i].start_block(), op.src_extents(i).start_block())
+        << "i == " << i;
+    EXPECT_EQ(old_extents[i].num_blocks(), op.src_extents(i).num_blocks())
+        << "i == " << i;
+  }
+
+  EXPECT_EQ(new_extents.size(), op.dst_extents_size());
+  for (int i = 0; i < op.dst_extents_size(); i++) {
+    EXPECT_EQ(new_extents[i].start_block(), op.dst_extents(i).start_block())
+        << "i == " << i;
+    EXPECT_EQ(new_extents[i].num_blocks(), op.dst_extents(i).num_blocks())
+        << "i == " << i;
+  }
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffSmallTest) {
+  // Test a BSDIFF operation from block 1 to block 2.
+  chromeos::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+  // Modify one byte in the new file.
+  data_blob[0]++;
+  EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_path_,
+      new_part_path_,
+      old_extents,
+      new_extents,
+      true,  // bsdiff_allowed
+      &data,
+      &op,
+      false));  // src_ops_allowed
+
+  EXPECT_FALSE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type());
+  EXPECT_FALSE(op.has_data_offset());
+  EXPECT_FALSE(op.has_data_length());
+  EXPECT_EQ(1, op.src_extents_size());
+  EXPECT_EQ(kBlockSize, op.src_length());
+  EXPECT_EQ(1, op.dst_extents_size());
+  EXPECT_EQ(kBlockSize, op.dst_length());
+  EXPECT_EQ(BlocksInExtents(op.src_extents()),
+            BlocksInExtents(op.dst_extents()));
+  EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedTest) {
+  // Same setup as the previous test, but this time BSDIFF operations are not
+  // allowed.
+  chromeos::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+  // Modify one byte in the new file.
+  data_blob[0]++;
+  EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_path_,
+      new_part_path_,
+      old_extents,
+      new_extents,
+      false,  // bsdiff_allowed
+      &data,
+      &op,
+      false));  // src_ops_allowed
+
+  EXPECT_FALSE(data.empty());
+
+  // The point of this test is that we don't use BSDIFF the way the above
+  // did. The rest of the details are to be caught in other tests.
+  EXPECT_TRUE(op.has_type());
+  EXPECT_NE(DeltaArchiveManifest_InstallOperation_Type_BSDIFF, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedMoveTest) {
+  chromeos::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+  EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_path_,
+      new_part_path_,
+      old_extents,
+      new_extents,
+      false,  // bsdiff_allowed
+      &data,
+      &op,
+      false));  // src_ops_allowed
+
+  EXPECT_TRUE(data.empty());
+
+  // The point of this test is that we can still use a MOVE for a file
+  // that is blacklisted.
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) {
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  // Make a blob that's just 1's that will compress well.
+  chromeos::Blob ones(kBlockSize, 1);
+
+  // Make a blob with random data that won't compress well.
+  chromeos::Blob random_data;
+  std::mt19937 gen(12345);
+  std::uniform_int_distribution<uint8_t> dis(0, 255);
+  for (uint32_t i = 0; i < kBlockSize; i++) {
+    random_data.push_back(dis(gen));
+  }
+
+  for (int i = 0; i < 2; i++) {
+    chromeos::Blob data_to_test = i == 0 ? random_data : ones;
+    // The old_extents will be initialized with 0.
+    EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize,
+                             data_to_test));
+
+    chromeos::Blob data;
+    DeltaArchiveManifest_InstallOperation op;
+    EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+        old_part_path_,
+        new_part_path_,
+        old_extents,
+        new_extents,
+        true,  // bsdiff_allowed
+        &data,
+        &op,
+        false));  // src_ops_allowed
+    EXPECT_FALSE(data.empty());
+
+    EXPECT_TRUE(op.has_type());
+    const DeltaArchiveManifest_InstallOperation_Type expected_type =
+        (i == 0 ? DeltaArchiveManifest_InstallOperation_Type_REPLACE :
+         DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+    EXPECT_EQ(expected_type, op.type());
+    EXPECT_FALSE(op.has_data_offset());
+    EXPECT_FALSE(op.has_data_length());
+    EXPECT_EQ(0, op.src_extents_size());
+    EXPECT_FALSE(op.has_src_length());
+    EXPECT_EQ(1, op.dst_extents_size());
+    EXPECT_EQ(data_to_test.size(), op.dst_length());
+    EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+  }
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceCopyTest) {
+  // Makes sure SOURCE_COPY operations are emitted whenever src_ops_allowed
+  // is true. It is the same setup as MoveSmallTest, which checks that
+  // the operation is well-formed.
+  chromeos::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(11, 1) };
+  vector<Extent> new_extents = { ExtentForRange(1, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+  EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_path_,
+      new_part_path_,
+      old_extents,
+      new_extents,
+      true,  // bsdiff_allowed
+      &data,
+      &op,
+      true));  // src_ops_allowed
+  EXPECT_TRUE(data.empty());
+
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) {
+  // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
+  // is true. It is the same setup as BsdiffSmallTest, which checks
+  // that the operation is well-formed.
+  chromeos::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = { ExtentForRange(1, 1) };
+  vector<Extent> new_extents = { ExtentForRange(2, 1) };
+
+  EXPECT_TRUE(WriteExtents(old_part_path_, old_extents, kBlockSize, data_blob));
+  // Modify one byte in the new file.
+  data_blob[0]++;
+  EXPECT_TRUE(WriteExtents(new_part_path_, new_extents, kBlockSize, data_blob));
+
+  chromeos::Blob data;
+  DeltaArchiveManifest_InstallOperation op;
+  EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_path_,
+      new_part_path_,
+      old_extents,
+      new_extents,
+      true,  // bsdiff_allowed
+      &data,
+      &op,
+      true));  // src_ops_allowed
+
+  EXPECT_FALSE(data.empty());
+  EXPECT_TRUE(op.has_type());
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF,
+            op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, IsNoopOperationTest) {
+  DeltaArchiveManifest_InstallOperation op;
+  op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+  EXPECT_FALSE(diff_utils::IsNoopOperation(op));
+  op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(3, 2);
+  *(op.add_dst_extents()) = ExtentForRange(3, 2);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(7, 5);
+  *(op.add_dst_extents()) = ExtentForRange(7, 5);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(20, 2);
+  *(op.add_dst_extents()) = ExtentForRange(20, 1);
+  *(op.add_dst_extents()) = ExtentForRange(21, 1);
+  EXPECT_TRUE(diff_utils::IsNoopOperation(op));
+  *(op.add_src_extents()) = ExtentForRange(24, 1);
+  *(op.add_dst_extents()) = ExtentForRange(25, 1);
+  EXPECT_FALSE(diff_utils::IsNoopOperation(op));
+}
+
+TEST_F(DeltaDiffUtilsTest, 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};
+  diff_utils::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/payload_generator/ext2_filesystem.cc b/payload_generator/ext2_filesystem.cc
new file mode 100644
index 0000000..b7276d5
--- /dev/null
+++ b/payload_generator/ext2_filesystem.cc
@@ -0,0 +1,343 @@
+// 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/ext2_filesystem.h"
+
+#include <et/com_err.h>
+#include <ext2fs/ext2_io.h>
+#include <ext2fs/ext2fs.h>
+
+#include <map>
+#include <set>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Processes all blocks belonging to an inode and adds them to the extent list.
+// This function should match the prototype expected by ext2fs_block_iterate2().
+int ProcessInodeAllBlocks(ext2_filsys fs,
+                          blk_t* blocknr,
+                          e2_blkcnt_t blockcnt,
+                          blk_t ref_blk,
+                          int ref_offset,
+                          void* priv) {
+  vector<Extent>* extents = static_cast<vector<Extent>*>(priv);
+  AppendBlockToExtents(extents, *blocknr);
+  return 0;
+}
+
+// Processes only indirect, double indirect or triple indirect metadata
+// blocks belonging to an inode. This function should match the prototype of
+// ext2fs_block_iterate2().
+int AddMetadataBlocks(ext2_filsys fs,
+                      blk_t* blocknr,
+                      e2_blkcnt_t blockcnt,
+                      blk_t ref_blk,
+                      int ref_offset,
+                      void* priv) {
+  set<uint64_t>* blocks = static_cast<set<uint64_t>*>(priv);
+  // If |blockcnt| is non-negative, |blocknr| points to the physical block
+  // number.
+  // If |blockcnt| is negative, it is one of the values: BLOCK_COUNT_IND,
+  // BLOCK_COUNT_DIND, BLOCK_COUNT_TIND or BLOCK_COUNT_TRANSLATOR and
+  // |blocknr| points to a block in the first three cases. The last case is
+  // only used by GNU Hurd, so we shouldn't see those cases here.
+  if (blockcnt == BLOCK_COUNT_IND || blockcnt == BLOCK_COUNT_DIND ||
+      blockcnt == BLOCK_COUNT_TIND) {
+    blocks->insert(*blocknr);
+  }
+  return 0;
+}
+
+struct UpdateFileAndAppendState {
+  std::map<ext2_ino_t, FilesystemInterface::File>* inodes = nullptr;
+  set<ext2_ino_t>* used_inodes = nullptr;
+  vector<FilesystemInterface::File>* files = nullptr;
+  ext2_filsys filsys;
+};
+
+int UpdateFileAndAppend(ext2_ino_t dir,
+                        int entry,
+                        struct ext2_dir_entry *dirent,
+                        int offset,
+                        int blocksize,
+                        char *buf,
+                        void *priv_data) {
+  UpdateFileAndAppendState* state =
+      static_cast<UpdateFileAndAppendState*>(priv_data);
+  uint32_t file_type = dirent->name_len >> 8;
+  // Directories can't have hard links, and they are added from the outer loop.
+  if (file_type == EXT2_FT_DIR)
+    return 0;
+
+  auto ino_file = state->inodes->find(dirent->inode);
+  if (ino_file == state->inodes->end())
+    return 0;
+  auto dir_file = state->inodes->find(dir);
+  if (dir_file == state->inodes->end())
+    return 0;
+  string basename(dirent->name, dirent->name_len & 0xff);
+  ino_file->second.name = dir_file->second.name;
+  if (dir_file->second.name != "/")
+    ino_file->second.name += "/";
+  ino_file->second.name += basename;
+
+  // Append this file to the output. If the file has a hard link, it will be
+  // added twice to the output, but with different names, which is ok. That will
+  // help identify all the versions of the same file.
+  state->files->push_back(ino_file->second);
+  state->used_inodes->insert(dirent->inode);
+  return 0;
+}
+
+}  // namespace
+
+unique_ptr<Ext2Filesystem> Ext2Filesystem::CreateFromFile(
+    const string& filename) {
+  if (filename.empty())
+    return nullptr;
+  unique_ptr<Ext2Filesystem> result(new Ext2Filesystem());
+  result->filename_ = filename;
+
+  errcode_t err = ext2fs_open(filename.c_str(),
+                              0,  // flags (read only)
+                              0,  // superblock block number
+                              0,  // block_size (autodetect)
+                              unix_io_manager,
+                              &result->filsys_);
+  if (err) {
+    LOG(ERROR) << "Opening ext2fs " << filename;
+    return nullptr;
+  }
+  return result;
+}
+
+Ext2Filesystem::~Ext2Filesystem() {
+  ext2fs_free(filsys_);
+}
+
+size_t Ext2Filesystem::GetBlockSize() const {
+  return filsys_->blocksize;
+}
+
+size_t Ext2Filesystem::GetBlockCount() const {
+  return ext2fs_blocks_count(filsys_->super);
+}
+
+bool Ext2Filesystem::GetFiles(vector<File>* files) const {
+  TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(filsys_));
+
+  ext2_inode_scan iscan;
+  TEST_AND_RETURN_FALSE_ERRCODE(
+      ext2fs_open_inode_scan(filsys_, 0 /* buffer_blocks */, &iscan));
+
+  std::map<ext2_ino_t, File> inodes;
+
+  // List of directories. We need to first parse all the files in a directory
+  // to later fix the absolute paths.
+  vector<ext2_ino_t> directories;
+
+  set<uint64_t> inode_blocks;
+
+  // Iterator
+  ext2_ino_t it_ino;
+  ext2_inode it_inode;
+
+  bool ok = true;
+  while (true) {
+    errcode_t error = ext2fs_get_next_inode(iscan, &it_ino, &it_inode);
+    if (error) {
+      LOG(ERROR) << "Failed to retrieve next inode (" << error << ")";
+      ok = false;
+      break;
+    }
+    if (it_ino == 0)
+      break;
+
+    // Skip inodes that are not in use.
+    if (!ext2fs_test_inode_bitmap(filsys_->inode_map, it_ino))
+      continue;
+
+    File& file = inodes[it_ino];
+    if (it_ino == EXT2_RESIZE_INO) {
+      file.name = "<group-descriptors>";
+    } else {
+      file.name = base::StringPrintf("<inode-%u>", it_ino);
+    }
+
+    memset(&file.file_stat, 0, sizeof(file.file_stat));
+    file.file_stat.st_ino = it_ino;
+    file.file_stat.st_mode = it_inode.i_mode;
+    file.file_stat.st_nlink = it_inode.i_links_count;
+    file.file_stat.st_uid = it_inode.i_uid;
+    file.file_stat.st_gid = it_inode.i_gid;
+    file.file_stat.st_size = it_inode.i_size;
+    file.file_stat.st_blksize = filsys_->blocksize;
+    file.file_stat.st_blocks = it_inode.i_blocks;
+    file.file_stat.st_atime = it_inode.i_atime;
+    file.file_stat.st_mtime = it_inode.i_mtime;
+    file.file_stat.st_ctime = it_inode.i_ctime;
+
+    bool is_dir = (ext2fs_check_directory(filsys_, it_ino) == 0);
+    if (is_dir)
+      directories.push_back(it_ino);
+
+    if (!ext2fs_inode_has_valid_blocks(&it_inode))
+      continue;
+
+    // Process the inode data and metadata blocks.
+    // For normal files, inode blocks are indirect, double indirect
+    // and triple indirect blocks (no data blocks). For directories and
+    // the journal, all blocks are considered metadata blocks.
+    int flags = it_ino < EXT2_GOOD_OLD_FIRST_INO ? 0 : BLOCK_FLAG_DATA_ONLY;
+    error = ext2fs_block_iterate2(filsys_, it_ino, flags,
+                                  nullptr,  // block_buf
+                                  ProcessInodeAllBlocks,
+                                  &file.extents);
+
+    if (error) {
+      LOG(ERROR) << "Failed to enumerate inode " << it_ino
+                << " blocks (" << error << ")";
+      continue;
+    }
+    if (it_ino >= EXT2_GOOD_OLD_FIRST_INO) {
+      ext2fs_block_iterate2(filsys_, it_ino, 0, nullptr,
+                            AddMetadataBlocks,
+                            &inode_blocks);
+    }
+  }
+  ext2fs_close_inode_scan(iscan);
+  if (!ok)
+    return false;
+
+  // The set of inodes already added to the output. There can be less elements
+  // here than in files since the later can contain repeated inodes due to
+  // hardlink files.
+  set<ext2_ino_t> used_inodes;
+
+  UpdateFileAndAppendState priv_data;
+  priv_data.inodes = &inodes;
+  priv_data.used_inodes = &used_inodes;
+  priv_data.files = files;
+  priv_data.filsys = filsys_;
+
+  files->clear();
+  // Iterate over all the files of each directory to update the name and add it.
+  for (ext2_ino_t dir_ino : directories) {
+    char* dir_name = nullptr;
+    errcode_t error = ext2fs_get_pathname(filsys_, dir_ino, 0, &dir_name);
+    if (error) {
+      // Not being able to read a directory name is not a fatal error, it is
+      // just skiped.
+      LOG(WARNING) << "Reading directory name on inode " << dir_ino
+                   << " (error " << error << ")";
+      inodes[dir_ino].name = base::StringPrintf("<dir-%u>", dir_ino);
+    } else {
+      inodes[dir_ino].name = dir_name;
+      files->push_back(inodes[dir_ino]);
+      used_inodes.insert(dir_ino);
+    }
+    ext2fs_free_mem(&dir_name);
+
+    error = ext2fs_dir_iterate2(
+        filsys_, dir_ino, 0, nullptr /* block_buf */,
+        UpdateFileAndAppend, &priv_data);
+    if (error) {
+      LOG(WARNING) << "Failed to enumerate files in directory "
+                   << inodes[dir_ino].name << " (error " << error << ")";
+    }
+  }
+
+  // Add <inode-blocks> file with the blocks that hold inodes.
+  File inode_file;
+  inode_file.name = "<inode-blocks>";
+  for (uint64_t block : inode_blocks) {
+    AppendBlockToExtents(&inode_file.extents, block);
+  }
+  files->push_back(inode_file);
+
+  // Add <free-spacce> blocs.
+  errcode_t error = ext2fs_read_block_bitmap(filsys_);
+  if (error) {
+    LOG(ERROR) << "Reading the blocks bitmap (error " << error << ")";
+  } else {
+    File free_space;
+    free_space.name = "<free-space>";
+    blk64_t blk_start = ext2fs_get_block_bitmap_start2(filsys_->block_map);
+    blk64_t blk_end = ext2fs_get_block_bitmap_end2(filsys_->block_map);
+    for (blk64_t block = blk_start; block < blk_end; block++) {
+      if (!ext2fs_test_block_bitmap2(filsys_->block_map, block))
+        AppendBlockToExtents(&free_space.extents, block);
+    }
+    files->push_back(free_space);
+  }
+
+  // Add all the unreachable files plus the pseudo-files with an inode. Since
+  // these inodes aren't files in the filesystem, ignore the empty ones.
+  for (const auto& ino_file : inodes) {
+    if (used_inodes.find(ino_file.first) != used_inodes.end())
+      continue;
+    if (ino_file.second.extents.empty())
+      continue;
+
+    File file = ino_file.second;
+    ExtentRanges ranges;
+    ranges.AddExtents(file.extents);
+    file.extents = ranges.GetExtentsForBlockCount(ranges.blocks());
+
+    files->push_back(file);
+  }
+
+  return true;
+}
+
+bool Ext2Filesystem::LoadSettings(chromeos::KeyValueStore* store) const {
+  // First search for the settings inode following symlinks if we find some.
+  ext2_ino_t ino_num = 0;
+  errcode_t err = ext2fs_namei_follow(
+      filsys_, EXT2_ROOT_INO /* root */, EXT2_ROOT_INO /* cwd */,
+      "/etc/update_engine.conf", &ino_num);
+  if (err != 0)
+    return false;
+
+  ext2_inode ino_data;
+  if (ext2fs_read_inode(filsys_, ino_num, &ino_data) != 0)
+    return false;
+
+  // Load the list of blocks and then the contents of the inodes.
+  vector<Extent> extents;
+  err = ext2fs_block_iterate2(filsys_, ino_num, BLOCK_FLAG_DATA_ONLY,
+                              nullptr,  // block_buf
+                              ProcessInodeAllBlocks,
+                              &extents);
+  if (err != 0)
+    return false;
+
+  chromeos::Blob blob;
+  uint64_t physical_size = BlocksInExtents(extents) * filsys_->blocksize;
+  // Sparse holes in the settings file are not supported.
+  if (EXT2_I_SIZE(&ino_data) > physical_size)
+    return false;
+  if (!utils::ReadExtents(filename_, extents, &blob, physical_size,
+                          filsys_->blocksize))
+    return false;
+
+  string text(blob.begin(), blob.begin() + EXT2_I_SIZE(&ino_data));
+  return store->LoadFromString(text);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/ext2_filesystem.h b/payload_generator/ext2_filesystem.h
new file mode 100644
index 0000000..66cb65f
--- /dev/null
+++ b/payload_generator/ext2_filesystem.h
@@ -0,0 +1,59 @@
+// 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_EXT2_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <ext2fs/ext2fs.h>
+
+namespace chromeos_update_engine {
+
+class Ext2Filesystem : public FilesystemInterface {
+ public:
+  // Creates an Ext2Filesystem from a ext2 formatted filesystem stored in a
+  // file. The file doesn't need to be loop-back mounted.
+  static std::unique_ptr<Ext2Filesystem> CreateFromFile(
+      const std::string& filename);
+  virtual ~Ext2Filesystem();
+
+  // FilesystemInterface overrides.
+  size_t GetBlockSize() const override;
+  size_t GetBlockCount() const override;
+
+  // GetFiles will return one FilesystemInterface::File for every file and every
+  // directory in the filesystem. Hard-linked files will appear in the list
+  // several times with the same list of blocks.
+  // On addition to actual files, it also returns these pseudo-files:
+  //  <free-space>: With all the unallocated data-blocks.
+  //  <inode-blocks>: Will all the data-blocks for second and third level inodes
+  //    of all the files.
+  //  <group-descriptors>: With the block group descriptor and their reserved
+  //    space.
+  //  <metadata>: With the rest of ext2 metadata blocks, such as superblocks
+  //    and bitmap tables.
+  bool GetFiles(std::vector<File>* files) const override;
+
+  bool LoadSettings(chromeos::KeyValueStore* store) const override;
+
+ private:
+  Ext2Filesystem() = default;
+
+  // The ext2 main data structure holding the filesystem.
+  ext2_filsys filsys_ = nullptr;
+
+  // The file where the filesystem is stored.
+  std::string filename_;
+
+  DISALLOW_COPY_AND_ASSIGN(Ext2Filesystem);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
diff --git a/payload_generator/ext2_filesystem_unittest.cc b/payload_generator/ext2_filesystem_unittest.cc
new file mode 100644
index 0000000..d2ea2b3
--- /dev/null
+++ b/payload_generator/ext2_filesystem_unittest.cc
@@ -0,0 +1,202 @@
+// 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/ext2_filesystem.h"
+
+#include <unistd.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::System;
+using std::map;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+uint64_t kDefaultFilesystemSize = 4 * 1024 * 1024;
+size_t kDefaultFilesystemBlockCount = 1024;
+size_t kDefaultFilesystemBlockSize = 4096;
+
+// Checks that all the blocks in |extents| are in the range [0, total_blocks).
+void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) {
+  for (const Extent& extent : extents) {
+    EXPECT_LE(0, extent.start_block());
+    EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks);
+  }
+}
+
+}  // namespace
+
+
+class Ext2FilesystemTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(utils::MakeTempFile("Ext2FilesystemTest-XXXXXX",
+                                    &fs_filename_, nullptr));
+    ASSERT_EQ(0, truncate(fs_filename_.c_str(), kDefaultFilesystemSize));
+  }
+
+  void TearDown() override {
+    unlink(fs_filename_.c_str());
+  }
+
+  string fs_filename_;
+};
+
+TEST_F(Ext2FilesystemTest, InvalidFilesystem) {
+  unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(fs_filename_);
+  ASSERT_EQ(nullptr, fs.get());
+
+  fs = Ext2Filesystem::CreateFromFile("/path/to/invalid/file");
+  ASSERT_EQ(nullptr, fs.get());
+}
+
+TEST_F(Ext2FilesystemTest, EmptyFilesystem) {
+  EXPECT_EQ(0, System(base::StringPrintf(
+      "/sbin/mkfs.ext2 -q -b %" PRIuS " -F %s",
+      kDefaultFilesystemBlockSize, fs_filename_.c_str())));
+  unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(fs_filename_);
+
+  ASSERT_NE(nullptr, fs.get());
+  EXPECT_EQ(kDefaultFilesystemBlockCount, fs->GetBlockCount());
+  EXPECT_EQ(kDefaultFilesystemBlockSize, fs->GetBlockSize());
+
+  vector<FilesystemInterface::File> files;
+  EXPECT_TRUE(fs->GetFiles(&files));
+
+  map<string, FilesystemInterface::File> map_files;
+  for (const auto& file : files) {
+    EXPECT_EQ(map_files.end(), map_files.find(file.name))
+        << "File " << file.name << " repeated in the list.";
+    map_files[file.name] = file;
+    ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+  }
+  EXPECT_EQ(2, map_files["/"].file_stat.st_ino);
+  EXPECT_FALSE(map_files["<free-space>"].extents.empty());
+}
+
+// This test parses the sample images generated during build time with the
+// "generate_image.sh" script. The expected conditions of each file in these
+// images is encoded in the file name, as defined in the mentioned script.
+TEST_F(Ext2FilesystemTest, ParseGeneratedImages) {
+  const vector<string> kGeneratedImages = {
+      "disk_ext2_1k.img",
+      "disk_ext2_4k.img" };
+  base::FilePath build_path = test_utils::GetBuildArtifactsPath().Append("gen");
+  for (const string& fs_name : kGeneratedImages) {
+    LOG(INFO) << "Testing " << fs_name;
+    unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(
+        build_path.Append(fs_name).value());
+    ASSERT_NE(nullptr, fs.get());
+
+    vector<FilesystemInterface::File> files;
+    map<string, FilesystemInterface::File> map_files;
+    set<string> filenames;
+    EXPECT_TRUE(fs->GetFiles(&files));
+    for (const auto& file : files) {
+      // Check no repeated files. We should parse hard-links with two different
+      // names.
+      EXPECT_EQ(map_files.end(), map_files.find(file.name))
+          << "File " << file.name << " repeated in the list.";
+      map_files[file.name] = file;
+      filenames.insert(file.name);
+      ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+    }
+
+    // Check that all the files are parsed, and the /removed file should not
+    // be included in the list.
+    set<string> kExpectedFiles = {
+        "/",
+        "/dir1",
+        "/dir1/file",
+        "/dir1/dir2",
+        "/dir1/dir2/file",
+        "/dir1/dir2/dir1",
+        "/empty-file",
+        "/link-hard-regular-16k",
+        "/link-long_symlink",
+        "/link-short_symlink",
+        "/lost+found",
+        "/regular-small",
+        "/regular-16k",
+        "/regular-32k-zeros",
+        "/regular-with_net_cap",
+        "/sparse_empty-10k",
+        "/sparse_empty-2blocks",
+        "/sparse-10000blocks",
+        "/sparse-16k-last_block",
+        "/sparse-16k-first_block",
+        "/sparse-16k-holes",
+        "<inode-blocks>",
+        "<free-space>",
+        "<group-descriptors>",
+    };
+    EXPECT_EQ(kExpectedFiles, filenames);
+
+    FilesystemInterface::File file;
+
+    // Small symlinks don't actually have data blocks.
+    EXPECT_TRUE(map_files["/link-short_symlink"].extents.empty());
+    EXPECT_EQ(1, BlocksInExtents(map_files["/link-long_symlink"].extents));
+
+    // Hard-links report the same list of blocks.
+    EXPECT_EQ(map_files["/link-hard-regular-16k"].extents,
+              map_files["/regular-16k"].extents);
+    EXPECT_FALSE(map_files["/regular-16k"].extents.empty());
+
+    // The number of blocks in these files doesn't depend on the
+    // block size.
+    EXPECT_TRUE(map_files["/empty-file"].extents.empty());
+    EXPECT_EQ(1, BlocksInExtents(map_files["/regular-small"].extents));
+    EXPECT_EQ(1, BlocksInExtents(map_files["/regular-with_net_cap"].extents));
+    EXPECT_TRUE(map_files["/sparse_empty-10k"].extents.empty());
+    EXPECT_TRUE(map_files["/sparse_empty-2blocks"].extents.empty());
+    EXPECT_EQ(1, BlocksInExtents(map_files["/sparse-16k-last_block"].extents));
+    EXPECT_EQ(1, BlocksInExtents(map_files["/sparse-16k-first_block"].extents));
+    EXPECT_EQ(2, BlocksInExtents(map_files["/sparse-16k-holes"].extents));
+  }
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsFailsTest) {
+  base::FilePath path = test_utils::GetBuildArtifactsPath().Append(
+      "gen/disk_ext2_1k.img");
+  unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(path.value());
+
+  chromeos::KeyValueStore store;
+  // disk_ext2_1k.img doesn't have the /etc/update_engine.conf file.
+  EXPECT_FALSE(fs->LoadSettings(&store));
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsWorksTest) {
+  base::FilePath path = test_utils::GetBuildArtifactsPath().Append(
+      "gen/disk_ext2_ue_settings.img");
+  unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(path.value());
+
+  chromeos::KeyValueStore store;
+  EXPECT_TRUE(fs->LoadSettings(&store));
+  string minor_version;
+  EXPECT_TRUE(store.GetString("PAYLOAD_MINOR_VERSION", &minor_version));
+  EXPECT_EQ("1234", minor_version);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/extent_ranges.cc b/payload_generator/extent_ranges.cc
new file mode 100644
index 0000000..d86bcfc
--- /dev/null
+++ b/payload_generator/extent_ranges.cc
@@ -0,0 +1,277 @@
+// Copyright (c) 2010 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/extent_ranges.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/payload_constants.h"
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+bool ExtentRanges::ExtentsOverlapOrTouch(const Extent& a, const Extent& b) {
+  if (a.start_block() == b.start_block())
+    return true;
+  if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+    return false;
+  if (a.start_block() < b.start_block()) {
+    return a.start_block() + a.num_blocks() >= b.start_block();
+  } else {
+    return b.start_block() + b.num_blocks() >= a.start_block();
+  }
+}
+
+bool ExtentRanges::ExtentsOverlap(const Extent& a, const Extent& b) {
+  if (a.start_block() == b.start_block())
+    return true;
+  if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+    return false;
+  if (a.start_block() < b.start_block()) {
+    return a.start_block() + a.num_blocks() > b.start_block();
+  } else {
+    return b.start_block() + b.num_blocks() > a.start_block();
+  }
+}
+
+void ExtentRanges::AddBlock(uint64_t block) {
+  AddExtent(ExtentForRange(block, 1));
+}
+
+void ExtentRanges::SubtractBlock(uint64_t block) {
+  SubtractExtent(ExtentForRange(block, 1));
+}
+
+namespace {
+
+Extent UnionOverlappingExtents(const Extent& first, const Extent& second) {
+  CHECK_NE(kSparseHole, first.start_block());
+  CHECK_NE(kSparseHole, second.start_block());
+  uint64_t start = std::min(first.start_block(), second.start_block());
+  uint64_t end = std::max(first.start_block() + first.num_blocks(),
+                          second.start_block() + second.num_blocks());
+  return ExtentForRange(start, end - start);
+}
+
+}  // namespace
+
+void ExtentRanges::AddExtent(Extent extent) {
+  if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+    return;
+
+  ExtentSet::iterator begin_del = extent_set_.end();
+  ExtentSet::iterator end_del = extent_set_.end();
+  uint64_t del_blocks = 0;
+  for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+       it != e; ++it) {
+    if (ExtentsOverlapOrTouch(*it, extent)) {
+      end_del = it;
+      ++end_del;
+      del_blocks += it->num_blocks();
+      if (begin_del == extent_set_.end())
+        begin_del = it;
+
+      extent = UnionOverlappingExtents(extent, *it);
+    }
+  }
+  extent_set_.erase(begin_del, end_del);
+  extent_set_.insert(extent);
+  blocks_ -= del_blocks;
+  blocks_ += extent.num_blocks();
+}
+
+namespace {
+// Returns base - subtractee (set subtraction).
+ExtentRanges::ExtentSet SubtractOverlappingExtents(const Extent& base,
+                                                   const Extent& subtractee) {
+  ExtentRanges::ExtentSet ret;
+  if (subtractee.start_block() > base.start_block()) {
+    ret.insert(ExtentForRange(base.start_block(),
+                              subtractee.start_block() - base.start_block()));
+  }
+  uint64_t base_end = base.start_block() + base.num_blocks();
+  uint64_t subtractee_end = subtractee.start_block() + subtractee.num_blocks();
+  if (base_end > subtractee_end) {
+    ret.insert(ExtentForRange(subtractee_end, base_end - subtractee_end));
+  }
+  return ret;
+}
+}  // namespace
+
+void ExtentRanges::SubtractExtent(const Extent& extent) {
+  if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+    return;
+
+  ExtentSet::iterator begin_del = extent_set_.end();
+  ExtentSet::iterator end_del = extent_set_.end();
+  uint64_t del_blocks = 0;
+  ExtentSet new_extents;
+  for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+       it != e; ++it) {
+    if (!ExtentsOverlap(*it, extent))
+      continue;
+
+    if (begin_del == extent_set_.end())
+      begin_del = it;
+    end_del = it;
+    ++end_del;
+
+    del_blocks += it->num_blocks();
+
+    ExtentSet subtraction = SubtractOverlappingExtents(*it, extent);
+    for (ExtentSet::iterator jt = subtraction.begin(), je = subtraction.end();
+         jt != je; ++jt) {
+      new_extents.insert(*jt);
+      del_blocks -= jt->num_blocks();
+    }
+  }
+  extent_set_.erase(begin_del, end_del);
+  extent_set_.insert(new_extents.begin(), new_extents.end());
+  blocks_ -= del_blocks;
+}
+
+void ExtentRanges::AddRanges(const ExtentRanges& ranges) {
+  for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+           e = ranges.extent_set_.end(); it != e; ++it) {
+    AddExtent(*it);
+  }
+}
+
+void ExtentRanges::SubtractRanges(const ExtentRanges& ranges) {
+  for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+           e = ranges.extent_set_.end(); it != e; ++it) {
+    SubtractExtent(*it);
+  }
+}
+
+void ExtentRanges::AddExtents(const vector<Extent>& extents) {
+  for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+       it != e; ++it) {
+    AddExtent(*it);
+  }
+}
+
+void ExtentRanges::SubtractExtents(const vector<Extent>& extents) {
+  for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+       it != e; ++it) {
+    SubtractExtent(*it);
+  }
+}
+
+void ExtentRanges::AddRepeatedExtents(
+    const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+  for (int i = 0, e = exts.size(); i != e; ++i) {
+    AddExtent(exts.Get(i));
+  }
+}
+
+void ExtentRanges::SubtractRepeatedExtents(
+    const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+  for (int i = 0, e = exts.size(); i != e; ++i) {
+    SubtractExtent(exts.Get(i));
+  }
+}
+
+void ExtentRanges::Dump() const {
+  LOG(INFO) << "ExtentRanges Dump. blocks: " << blocks_;
+  for (ExtentSet::const_iterator it = extent_set_.begin(),
+           e = extent_set_.end();
+       it != e; ++it) {
+    LOG(INFO) << "{" << it->start_block() << ", " << it->num_blocks() << "}";
+  }
+}
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks) {
+  Extent ret;
+  ret.set_start_block(start_block);
+  ret.set_num_blocks(num_blocks);
+  return ret;
+}
+
+vector<Extent> ExtentRanges::GetExtentsForBlockCount(
+    uint64_t count) const {
+  vector<Extent> out;
+  if (count == 0)
+    return out;
+  uint64_t out_blocks = 0;
+  CHECK(count <= blocks_);
+  for (ExtentSet::const_iterator it = extent_set_.begin(),
+           e = extent_set_.end();
+       it != e; ++it) {
+    const uint64_t blocks_needed = count - out_blocks;
+    const Extent& extent = *it;
+    out.push_back(extent);
+    out_blocks += extent.num_blocks();
+    if (extent.num_blocks() < blocks_needed)
+      continue;
+    if (extent.num_blocks() == blocks_needed)
+      break;
+    // If we get here, we just added the last extent needed, but it's too big
+    out_blocks -= extent.num_blocks();
+    out_blocks += blocks_needed;
+    out.back().set_num_blocks(blocks_needed);
+    break;
+  }
+  return out;
+}
+
+vector<Extent> FilterExtentRanges(const std::vector<Extent>& extents,
+                                  const ExtentRanges& ranges) {
+  vector<Extent> result;
+  const ExtentRanges::ExtentSet& extent_set = ranges.extent_set();
+  for (Extent extent : extents) {
+    // The extents are sorted by the start_block. We want to iterate all the
+    // Extents in the ExtentSet possibly overlapping the current |extent|. This
+    // is achieved by looking from the extent whose start_block is *lower* than
+    // the extent.start_block() up to the greatest extent whose start_block is
+    // lower than extent.start_block() + extent.num_blocks().
+    auto lower = extent_set.lower_bound(extent);
+    // We need to decrement the lower_bound to look at the extent that could
+    // overlap the beginning of the current |extent|.
+    if (lower != extent_set.begin())
+      lower--;
+    auto upper = extent_set.lower_bound(
+        ExtentForRange(extent.start_block() + extent.num_blocks(), 0));
+    for (auto iter = lower; iter != upper; ++iter) {
+      if (!ExtentRanges::ExtentsOverlap(extent, *iter))
+        continue;
+      if (iter->start_block() <= extent.start_block()) {
+        // We need to cut blocks from the beginning of the |extent|.
+        uint64_t cut_blocks = iter->start_block() + iter->num_blocks() -
+            extent.start_block();
+        if (cut_blocks >= extent.num_blocks()) {
+          extent.set_num_blocks(0);
+          break;
+        }
+        extent = ExtentForRange(extent.start_block() + cut_blocks,
+                                extent.num_blocks() - cut_blocks);
+      } else {
+        // We need to cut blocks on the middle of the extent, possible up to the
+        // end of it.
+        result.push_back(
+            ExtentForRange(extent.start_block(),
+                           iter->start_block() - extent.start_block()));
+        uint64_t new_start = iter->start_block() + iter->num_blocks();
+        uint64_t old_end = extent.start_block() + extent.num_blocks();
+        if (new_start >= old_end) {
+          extent.set_num_blocks(0);
+          break;
+        }
+        extent = ExtentForRange(new_start, old_end - new_start);
+      }
+    }
+    if (extent.num_blocks() > 0)
+      result.push_back(extent);
+  }
+  return result;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/extent_ranges.h b/payload_generator/extent_ranges.h
new file mode 100644
index 0000000..fc15149
--- /dev/null
+++ b/payload_generator/extent_ranges.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2010 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_EXTENT_RANGES_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// An ExtentRanges object represents an unordered collection of extents (and
+// therefore blocks). Such an object may be modified by adding or subtracting
+// blocks (think: set addition or set subtraction). Note that ExtentRanges
+// ignores sparse hole extents mostly to avoid confusion between extending a
+// sparse hole range vs. set addition but also to ensure that the delta
+// generator doesn't use sparse holes as scratch space.
+
+namespace chromeos_update_engine {
+
+struct ExtentLess {
+  bool operator()(const Extent& x, const Extent& y) const {
+    return x.start_block() < y.start_block();
+  }
+};
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks);
+
+class ExtentRanges {
+ public:
+  typedef std::set<Extent, ExtentLess> ExtentSet;
+
+  ExtentRanges() : blocks_(0) {}
+  void AddBlock(uint64_t block);
+  void SubtractBlock(uint64_t block);
+  void AddExtent(Extent extent);
+  void SubtractExtent(const Extent& extent);
+  void AddExtents(const std::vector<Extent>& extents);
+  void SubtractExtents(const std::vector<Extent>& extents);
+  void AddRepeatedExtents(
+      const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+  void SubtractRepeatedExtents(
+      const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+  void AddRanges(const ExtentRanges& ranges);
+  void SubtractRanges(const ExtentRanges& ranges);
+
+  static bool ExtentsOverlapOrTouch(const Extent& a, const Extent& b);
+  static bool ExtentsOverlap(const Extent& a, const Extent& b);
+
+  // Dumps contents to the log file. Useful for debugging.
+  void Dump() const;
+
+  uint64_t blocks() const { return blocks_; }
+  const ExtentSet& extent_set() const { return extent_set_; }
+
+  // Returns an ordered vector of extents for |count| blocks,
+  // using extents in extent_set_. The returned extents are not
+  // removed from extent_set_. |count| must be less than or equal to
+  // the number of blocks in this extent set.
+  std::vector<Extent> GetExtentsForBlockCount(uint64_t count) const;
+
+ private:
+  ExtentSet extent_set_;
+  uint64_t blocks_;
+};
+
+// Filters out from the passed list of extents |extents| all the blocks in the
+// ExtentRanges set. Note that the order of the blocks in |extents| is preserved
+// omitting blocks present in the ExtentRanges |ranges|.
+std::vector<Extent> FilterExtentRanges(const std::vector<Extent>& extents,
+                                       const ExtentRanges& ranges);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
diff --git a/payload_generator/extent_ranges_unittest.cc b/payload_generator/extent_ranges_unittest.cc
new file mode 100644
index 0000000..b7036a5
--- /dev/null
+++ b/payload_generator/extent_ranges_unittest.cc
@@ -0,0 +1,314 @@
+// Copyright (c) 2010 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/extent_ranges.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentRangesTest : public ::testing::Test {};
+
+namespace {
+void ExpectRangeEq(const ExtentRanges& ranges,
+                   const uint64_t* expected,
+                   size_t sz,
+                   int line) {
+  uint64_t blocks = 0;
+  for (size_t i = 1; i < sz; i += 2) {
+    blocks += expected[i];
+  }
+  EXPECT_EQ(blocks, ranges.blocks()) << "line: " << line;
+
+  const ExtentRanges::ExtentSet& result = ranges.extent_set();
+  ExtentRanges::ExtentSet::const_iterator it = result.begin();
+  for (size_t i = 0; i < sz; i += 2) {
+    EXPECT_FALSE(it == result.end()) << "line: " << line;
+    EXPECT_EQ(expected[i], it->start_block()) << "line: " << line;
+    EXPECT_EQ(expected[i + 1], it->num_blocks()) << "line: " << line;
+    ++it;
+  }
+}
+
+#define EXPECT_RANGE_EQ(ranges, var)                            \
+  do {                                                          \
+    ExpectRangeEq(ranges, var, arraysize(var), __LINE__);       \
+  } while (0)
+
+void ExpectRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+                                uint64_t b_start, uint64_t b_num) {
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+                                                                 a_num),
+                                                  ExtentForRange(b_start,
+                                                                 b_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+                                                                 b_num),
+                                                  ExtentForRange(a_start,
+                                                                 a_num)));
+}
+
+void ExpectFalseRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+                                     uint64_t b_start, uint64_t b_num) {
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+                                                                  a_num),
+                                                   ExtentForRange(b_start,
+                                                                  b_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+                                                                  b_num),
+                                                   ExtentForRange(a_start,
+                                                                  a_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+                                                           a_num),
+                                            ExtentForRange(b_start,
+                                                           b_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+                                                           b_num),
+                                            ExtentForRange(a_start,
+                                                           a_num)));
+}
+
+void ExpectRangesOverlap(uint64_t a_start, uint64_t a_num,
+                         uint64_t b_start, uint64_t b_num) {
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+                                                          a_num),
+                                           ExtentForRange(b_start,
+                                                          b_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+                                                          b_num),
+                                           ExtentForRange(a_start,
+                                                          a_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+                                                                 a_num),
+                                                  ExtentForRange(b_start,
+                                                                 b_num)));
+  EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+                                                                 b_num),
+                                                  ExtentForRange(a_start,
+                                                                 a_num)));
+}
+
+void ExpectFalseRangesOverlap(uint64_t a_start, uint64_t a_num,
+                              uint64_t b_start, uint64_t b_num) {
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+                                                           a_num),
+                                            ExtentForRange(b_start,
+                                                           b_num)));
+  EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+                                                           b_num),
+                                            ExtentForRange(a_start,
+                                                           a_num)));
+}
+
+}  // namespace
+
+TEST(ExtentRangesTest, ExtentsOverlapTest) {
+  ExpectRangesOverlapOrTouch(10, 20, 30, 10);
+  ExpectRangesOverlap(10, 20, 25, 10);
+  ExpectFalseRangesOverlapOrTouch(10, 20, 35, 10);
+  ExpectFalseRangesOverlap(10, 20, 30, 10);
+  ExpectRangesOverlap(12, 4, 12, 3);
+
+  ExpectRangesOverlapOrTouch(kSparseHole, 2, kSparseHole, 3);
+  ExpectRangesOverlap(kSparseHole, 2, kSparseHole, 3);
+  ExpectFalseRangesOverlapOrTouch(kSparseHole, 2, 10, 3);
+  ExpectFalseRangesOverlapOrTouch(10, 2, kSparseHole, 3);
+  ExpectFalseRangesOverlap(kSparseHole, 2, 10, 3);
+  ExpectFalseRangesOverlap(10, 2, kSparseHole, 3);
+}
+
+TEST(ExtentRangesTest, SimpleTest) {
+  ExtentRanges ranges;
+  {
+    static const uint64_t expected[] = {};
+    // Can't use arraysize() on 0-length arrays:
+    ExpectRangeEq(ranges, expected, 0, __LINE__);
+  }
+  ranges.SubtractBlock(2);
+  {
+    static const uint64_t expected[] = {};
+    // Can't use arraysize() on 0-length arrays:
+    ExpectRangeEq(ranges, expected, 0, __LINE__);
+  }
+
+  ranges.AddBlock(0);
+  ranges.Dump();
+  ranges.AddBlock(1);
+  ranges.AddBlock(3);
+
+  {
+    static const uint64_t expected[] = {0, 2, 3, 1};
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.AddBlock(2);
+  {
+    static const uint64_t expected[] = {0, 4};
+    EXPECT_RANGE_EQ(ranges, expected);
+    ranges.AddBlock(kSparseHole);
+    EXPECT_RANGE_EQ(ranges, expected);
+    ranges.SubtractBlock(kSparseHole);
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.SubtractBlock(2);
+  {
+    static const uint64_t expected[] = {0, 2, 3, 1};
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+
+  for (uint64_t i = 100; i < 1000; i += 100) {
+    ranges.AddExtent(ExtentForRange(i, 50));
+  }
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 50, 300, 50, 400, 50,
+      500, 50, 600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+
+  ranges.SubtractExtent(ExtentForRange(210, 410 - 210));
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+      600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.AddExtent(ExtentForRange(100000, 0));
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+      600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+  ranges.SubtractExtent(ExtentForRange(3, 0));
+  {
+    static const uint64_t expected[] = {
+      0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+      600, 50, 700, 50, 800, 50, 900, 50
+    };
+    EXPECT_RANGE_EQ(ranges, expected);
+  }
+}
+
+TEST(ExtentRangesTest, MultipleRanges) {
+  ExtentRanges ranges_a, ranges_b;
+  ranges_a.AddBlock(0);
+  ranges_b.AddBlock(4);
+  ranges_b.AddBlock(3);
+  {
+    uint64_t expected[] = {3, 2};
+    EXPECT_RANGE_EQ(ranges_b, expected);
+  }
+  ranges_a.AddRanges(ranges_b);
+  {
+    uint64_t expected[] = {0, 1, 3, 2};
+    EXPECT_RANGE_EQ(ranges_a, expected);
+  }
+  ranges_a.SubtractRanges(ranges_b);
+  {
+    uint64_t expected[] = {0, 1};
+    EXPECT_RANGE_EQ(ranges_a, expected);
+  }
+  {
+    uint64_t expected[] = {3, 2};
+    EXPECT_RANGE_EQ(ranges_b, expected);
+  }
+}
+
+TEST(ExtentRangesTest, GetExtentsForBlockCountTest) {
+  ExtentRanges ranges;
+  ranges.AddExtents(vector<Extent>(1, ExtentForRange(10, 30)));
+  {
+    vector<Extent> zero_extents = ranges.GetExtentsForBlockCount(0);
+    EXPECT_TRUE(zero_extents.empty());
+  }
+  ::google::protobuf::RepeatedPtrField<Extent> rep_field;
+  *rep_field.Add() = ExtentForRange(30, 40);
+  ranges.AddRepeatedExtents(rep_field);
+  ranges.SubtractExtents(vector<Extent>(1, ExtentForRange(20, 10)));
+  *rep_field.Mutable(0) = ExtentForRange(50, 10);
+  ranges.SubtractRepeatedExtents(rep_field);
+  EXPECT_EQ(40, ranges.blocks());
+
+  for (int i = 0; i < 2; i++) {
+    vector<Extent> expected(2);
+    expected[0] = ExtentForRange(10, 10);
+    expected[1] = ExtentForRange(30, i == 0 ? 10 : 20);
+    vector<Extent> actual =
+        ranges.GetExtentsForBlockCount(10 + expected[1].num_blocks());
+    EXPECT_EQ(expected.size(), actual.size());
+    for (vector<Extent>::size_type j = 0, e = expected.size(); j != e; ++j) {
+      EXPECT_EQ(expected[j].start_block(), actual[j].start_block())
+          << "j = " << j;
+      EXPECT_EQ(expected[j].num_blocks(), actual[j].num_blocks())
+          << "j = " << j;
+    }
+  }
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesEmptyRanges) {
+  ExtentRanges ranges;
+  EXPECT_EQ(vector<Extent>(),
+            FilterExtentRanges(vector<Extent>(), ranges));
+  EXPECT_EQ(
+      vector<Extent>{ ExtentForRange(50, 10) },
+      FilterExtentRanges(vector<Extent>{ ExtentForRange(50, 10) }, ranges));
+  // Check that the empty Extents are ignored.
+  EXPECT_EQ(
+      (vector<Extent>{ ExtentForRange(10, 10), ExtentForRange(20, 10) }),
+      FilterExtentRanges(vector<Extent>{
+           ExtentForRange(10, 10),
+           ExtentForRange(3, 0),
+           ExtentForRange(20, 10) }, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesMultipleRanges) {
+  // Two overlaping extents, with three ranges to remove.
+  vector<Extent> extents {
+      ExtentForRange(10, 100),
+      ExtentForRange(30, 100) };
+  ExtentRanges ranges;
+  // This overlaps the beginning of the second extent.
+  ranges.AddExtent(ExtentForRange(28, 3));
+  ranges.AddExtent(ExtentForRange(50, 10));
+  ranges.AddExtent(ExtentForRange(70, 10));
+  // This overlaps the end of the second extent.
+  ranges.AddExtent(ExtentForRange(108, 6));
+  EXPECT_EQ(
+      (vector<Extent>{
+           // For the first extent:
+           ExtentForRange(10, 18),
+           ExtentForRange(31, 19),
+           ExtentForRange(60, 10),
+           ExtentForRange(80, 28),
+           // For the second extent:
+           ExtentForRange(31, 19),
+           ExtentForRange(60, 10),
+           ExtentForRange(80, 28),
+           ExtentForRange(114, 16)}),
+      FilterExtentRanges(extents, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesOvelapping) {
+  ExtentRanges ranges;
+  ranges.AddExtent(ExtentForRange(10, 3));
+  ranges.AddExtent(ExtentForRange(20, 5));
+  // Requested extent overlaps with one of the ranges.
+  EXPECT_EQ(vector<Extent>(),
+            FilterExtentRanges(vector<Extent>{
+                                   ExtentForRange(10, 1),
+                                   ExtentForRange(22, 1) },
+                               ranges));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/extent_utils.cc b/payload_generator/extent_utils.cc
new file mode 100644
index 0000000..caacb38
--- /dev/null
+++ b/payload_generator/extent_utils.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2009 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/extent_utils.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void AppendBlockToExtents(vector<Extent>* extents, uint64_t block) {
+  // First try to extend the last extent in |extents|, if any.
+  if (!extents->empty()) {
+    Extent& extent = extents->back();
+    uint64_t next_block = extent.start_block() == kSparseHole ?
+        kSparseHole : extent.start_block() + extent.num_blocks();
+    if (next_block == block) {
+      extent.set_num_blocks(extent.num_blocks() + 1);
+      return;
+    }
+  }
+  // If unable to extend the last extent, append a new single-block extent.
+  Extent new_extent;
+  new_extent.set_start_block(block);
+  new_extent.set_num_blocks(1);
+  extents->push_back(new_extent);
+}
+
+Extent GetElement(const vector<Extent>& collection, size_t index) {
+  return collection[index];
+}
+
+Extent GetElement(
+    const google::protobuf::RepeatedPtrField<Extent>& collection,
+    size_t index) {
+  return collection.Get(index);
+}
+
+void ExtendExtents(
+    google::protobuf::RepeatedPtrField<Extent>* extents,
+    const google::protobuf::RepeatedPtrField<Extent>& extents_to_add) {
+  vector<Extent> extents_vector;
+  vector<Extent> extents_to_add_vector;
+  ExtentsToVector(*extents, &extents_vector);
+  ExtentsToVector(extents_to_add, &extents_to_add_vector);
+  extents_vector.insert(extents_vector.end(),
+                        extents_to_add_vector.begin(),
+                        extents_to_add_vector.end());
+  NormalizeExtents(&extents_vector);
+  extents->Clear();
+  StoreExtents(extents_vector, extents);
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const vector<Extent>& extents,
+                  google::protobuf::RepeatedPtrField<Extent>* out) {
+  for (const Extent& extent : extents) {
+    Extent* new_extent = out->Add();
+    *new_extent = extent;
+  }
+}
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+                     vector<Extent>* out_vector) {
+  out_vector->clear();
+  for (int i = 0; i < extents.size(); i++) {
+    out_vector->push_back(extents.Get(i));
+  }
+}
+
+void NormalizeExtents(vector<Extent>* extents) {
+  vector<Extent> new_extents;
+  for (const Extent& curr_ext : *extents) {
+    if (new_extents.empty()) {
+      new_extents.push_back(curr_ext);
+      continue;
+    }
+    Extent& last_ext = new_extents.back();
+    if (last_ext.start_block() + last_ext.num_blocks() ==
+        curr_ext.start_block()) {
+      // If the extents are touching, we want to combine them.
+      last_ext.set_num_blocks(last_ext.num_blocks() + curr_ext.num_blocks());
+    } else {
+      // Otherwise just include the extent as is.
+      new_extents.push_back(curr_ext);
+    }
+  }
+  *extents = new_extents;
+}
+
+vector<Extent> ExtentsSublist(const vector<Extent>& extents,
+                              uint64_t block_offset, uint64_t block_count) {
+  vector<Extent> result;
+  uint64_t scanned_blocks = 0;
+  if (block_count == 0)
+    return result;
+  uint64_t end_block_offset = block_offset + block_count;
+  for (const Extent& extent : extents) {
+    // The loop invariant is that if |extents| has enough blocks, there's
+    // still some extent to add to |result|. This implies that at the beginning
+    // of the loop scanned_blocks < block_offset + block_count.
+    if (scanned_blocks + extent.num_blocks() > block_offset) {
+      // This case implies that |extent| has some overlapping with the requested
+      // subsequence.
+      uint64_t new_start = extent.start_block();
+      uint64_t new_num_blocks = extent.num_blocks();
+      if (scanned_blocks + new_num_blocks > end_block_offset) {
+        // Cut the end part of the extent.
+        new_num_blocks = end_block_offset - scanned_blocks;
+      }
+      if (block_offset > scanned_blocks) {
+        // Cut the begin part of the extent.
+        new_num_blocks -= block_offset - scanned_blocks;
+        new_start += block_offset - scanned_blocks;
+      }
+      result.push_back(ExtentForRange(new_start, new_num_blocks));
+    }
+    scanned_blocks += extent.num_blocks();
+    if (scanned_blocks >= end_block_offset)
+      break;
+  }
+  return result;
+}
+
+bool operator==(const Extent& a, const Extent& b) {
+  return a.start_block() == b.start_block() && a.num_blocks() == b.num_blocks();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/extent_utils.h b/payload_generator/extent_utils.h
new file mode 100644
index 0000000..eeeed40
--- /dev/null
+++ b/payload_generator/extent_utils.h
@@ -0,0 +1,89 @@
+// 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_EXTENT_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
+
+#include <vector>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/update_metadata.pb.h"
+
+// Utility functions for manipulating Extents and lists of blocks.
+
+namespace chromeos_update_engine {
+
+// |block| must either be the next block in the last extent or a block
+// in the next extent. This function will not handle inserting block
+// into an arbitrary place in the extents.
+void AppendBlockToExtents(std::vector<Extent>* extents, uint64_t block);
+
+// Get/SetElement are intentionally overloaded so that templated functions
+// can accept either type of collection of Extents.
+Extent GetElement(const std::vector<Extent>& collection, size_t index);
+Extent GetElement(
+    const google::protobuf::RepeatedPtrField<Extent>& collection,
+    size_t index);
+
+// Return the total number of blocks in a collection (vector or
+// RepeatedPtrField) of Extents.
+template<typename T>
+uint64_t BlocksInExtents(const T& collection) {
+  uint64_t ret = 0;
+  for (size_t i = 0; i < static_cast<size_t>(collection.size()); ++i) {
+    ret += GetElement(collection, i).num_blocks();
+  }
+  return ret;
+}
+
+// Takes a collection (vector or RepeatedPtrField) of Extent and
+// returns a vector of the blocks referenced, in order.
+template<typename T>
+std::vector<uint64_t> ExpandExtents(const T& extents) {
+  std::vector<uint64_t> ret;
+  for (size_t i = 0, e = static_cast<size_t>(extents.size()); i != e; ++i) {
+    const Extent extent = GetElement(extents, i);
+    if (extent.start_block() == kSparseHole) {
+      ret.resize(ret.size() + extent.num_blocks(), kSparseHole);
+    } else {
+      for (uint64_t block = extent.start_block();
+           block < (extent.start_block() + extent.num_blocks()); block++) {
+        ret.push_back(block);
+      }
+    }
+  }
+  return ret;
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const std::vector<Extent>& extents,
+                  google::protobuf::RepeatedPtrField<Extent>* out);
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+                     std::vector<Extent>* out_vector);
+
+// Takes a pointer to extents |extents| and extents |extents_to_add|, and
+// merges them by adding |extents_to_add| to |extents| and normalizing.
+void ExtendExtents(
+  google::protobuf::RepeatedPtrField<Extent>* extents,
+  const google::protobuf::RepeatedPtrField<Extent>& extents_to_add);
+
+// Takes a vector of extents and normalizes those extents. Expects the extents
+// to be sorted by start block. E.g. if |extents| is [(1, 2), (3, 5), (10, 2)]
+// then |extents| will be changed to [(1, 7), (10, 2)].
+void NormalizeExtents(std::vector<Extent>* extents);
+
+// Return a subsequence of the list of blocks passed. Both the passed list of
+// blocks |extents| and the return value are expressed as a list of Extent, not
+// blocks. The returned list skips the first |block_offset| blocks from the
+// |extents| and cotains |block_count| blocks (or less if |extents| is shorter).
+std::vector<Extent> ExtentsSublist(const std::vector<Extent>& extents,
+                                   uint64_t block_offset, uint64_t block_count);
+
+bool operator==(const Extent& a, const Extent& b);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
diff --git a/payload_generator/extent_utils_unittest.cc b/payload_generator/extent_utils_unittest.cc
new file mode 100644
index 0000000..188164d
--- /dev/null
+++ b/payload_generator/extent_utils_unittest.cc
@@ -0,0 +1,152 @@
+// 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/extent_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/test_utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentUtilsTest : public ::testing::Test {};
+
+TEST(ExtentUtilsTest, AppendSparseToExtentsTest) {
+  vector<Extent> extents;
+
+  EXPECT_EQ(0, extents.size());
+  AppendBlockToExtents(&extents, kSparseHole);
+  EXPECT_EQ(1, extents.size());
+  AppendBlockToExtents(&extents, 0);
+  EXPECT_EQ(2, extents.size());
+  AppendBlockToExtents(&extents, kSparseHole);
+  AppendBlockToExtents(&extents, kSparseHole);
+
+  ASSERT_EQ(3, extents.size());
+  EXPECT_EQ(kSparseHole, extents[0].start_block());
+  EXPECT_EQ(1, extents[0].num_blocks());
+  EXPECT_EQ(0, extents[1].start_block());
+  EXPECT_EQ(1, extents[1].num_blocks());
+  EXPECT_EQ(kSparseHole, extents[2].start_block());
+  EXPECT_EQ(2, extents[2].num_blocks());
+}
+
+TEST(ExtentUtilsTest, BlocksInExtentsTest) {
+  {
+    vector<Extent> extents;
+    EXPECT_EQ(0, BlocksInExtents(extents));
+    extents.push_back(ExtentForRange(0, 1));
+    EXPECT_EQ(1, BlocksInExtents(extents));
+    extents.push_back(ExtentForRange(23, 55));
+    EXPECT_EQ(56, BlocksInExtents(extents));
+    extents.push_back(ExtentForRange(1, 2));
+    EXPECT_EQ(58, BlocksInExtents(extents));
+  }
+  {
+    google::protobuf::RepeatedPtrField<Extent> extents;
+    EXPECT_EQ(0, BlocksInExtents(extents));
+    *extents.Add() = ExtentForRange(0, 1);
+    EXPECT_EQ(1, BlocksInExtents(extents));
+    *extents.Add() = ExtentForRange(23, 55);
+    EXPECT_EQ(56, BlocksInExtents(extents));
+    *extents.Add() = ExtentForRange(1, 2);
+    EXPECT_EQ(58, BlocksInExtents(extents));
+  }
+}
+
+TEST(ExtentUtilsTest, ExtendExtentsTest) {
+  DeltaArchiveManifest_InstallOperation first_op;
+  *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+  *(first_op.add_src_extents()) = ExtentForRange(3, 1);
+
+  DeltaArchiveManifest_InstallOperation second_op;
+  *(second_op.add_src_extents()) = ExtentForRange(4, 2);
+  *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+
+  ExtendExtents(first_op.mutable_src_extents(), second_op.src_extents());
+  vector<Extent> first_op_vec;
+  ExtentsToVector(first_op.src_extents(), &first_op_vec);
+  EXPECT_EQ((vector<Extent>{
+      ExtentForRange(1, 1),
+      ExtentForRange(3, 3),
+      ExtentForRange(8, 2)}), first_op_vec);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsSimpleList) {
+  // Make sure it works when there's just one extent.
+  vector<Extent> extents;
+  NormalizeExtents(&extents);
+  EXPECT_EQ(0, extents.size());
+
+  extents = { ExtentForRange(0, 3) };
+  NormalizeExtents(&extents);
+  EXPECT_EQ(1, extents.size());
+  EXPECT_EQ(ExtentForRange(0, 3), extents[0]);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsTest) {
+  vector<Extent> extents = {
+      ExtentForRange(0, 3),
+      ExtentForRange(3, 2),
+      ExtentForRange(5, 1),
+      ExtentForRange(8, 4),
+      ExtentForRange(13, 1),
+      ExtentForRange(14, 2)
+  };
+  NormalizeExtents(&extents);
+  EXPECT_EQ(3, extents.size());
+  EXPECT_EQ(ExtentForRange(0, 6), extents[0]);
+  EXPECT_EQ(ExtentForRange(8, 4), extents[1]);
+  EXPECT_EQ(ExtentForRange(13, 3), extents[2]);
+}
+
+TEST(ExtentUtilsTest, ExtentsSublistTest) {
+  vector<Extent> extents = {
+      ExtentForRange(10, 10),
+      ExtentForRange(30, 10),
+      ExtentForRange(50, 10)
+  };
+
+  // Simple empty result cases.
+  EXPECT_EQ(vector<Extent>(),
+            ExtentsSublist(extents, 1000, 20));
+  EXPECT_EQ(vector<Extent>(),
+            ExtentsSublist(extents, 5, 0));
+  EXPECT_EQ(vector<Extent>(),
+            ExtentsSublist(extents, 30, 1));
+
+  // Normal test cases.
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(13, 2) },
+            ExtentsSublist(extents, 3, 2));
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(15, 5) },
+            ExtentsSublist(extents, 5, 5));
+  EXPECT_EQ((vector<Extent>{ ExtentForRange(15, 5), ExtentForRange(30, 5) }),
+            ExtentsSublist(extents, 5, 10));
+  EXPECT_EQ((vector<Extent>{
+                 ExtentForRange(13, 7),
+                 ExtentForRange(30, 10),
+                 ExtentForRange(50, 3), }),
+            ExtentsSublist(extents, 3, 20));
+
+  // Extact match case.
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(30, 10) },
+            ExtentsSublist(extents, 10, 10));
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(50, 10) },
+            ExtentsSublist(extents, 20, 10));
+
+  // Cases where the requested num_blocks is too big.
+  EXPECT_EQ(vector<Extent>{ ExtentForRange(53, 7) },
+            ExtentsSublist(extents, 23, 100));
+  EXPECT_EQ((vector<Extent>{ ExtentForRange(34, 6), ExtentForRange(50, 10) }),
+            ExtentsSublist(extents, 14, 100));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/fake_filesystem.cc b/payload_generator/fake_filesystem.cc
new file mode 100644
index 0000000..00911e1
--- /dev/null
+++ b/payload_generator/fake_filesystem.cc
@@ -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.
+
+#include "update_engine/payload_generator/fake_filesystem.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+FakeFilesystem::FakeFilesystem(uint64_t block_size, uint64_t block_count) :
+    block_size_(block_size),
+    block_count_(block_count) {
+}
+
+size_t FakeFilesystem::GetBlockSize() const {
+  return block_size_;
+}
+
+size_t FakeFilesystem::GetBlockCount() const {
+  return block_count_;
+}
+
+bool FakeFilesystem::GetFiles(std::vector<File>* files) const {
+  *files = files_;
+  return true;
+}
+
+void FakeFilesystem::AddFile(const std::string& filename,
+                             const std::vector<Extent> extents) {
+  File file;
+  file.name = filename;
+  file.extents = extents;
+  for (const Extent& extent : extents) {
+    EXPECT_LE(0, extent.start_block());
+    EXPECT_LE(extent.start_block() + extent.num_blocks(), block_count_);
+  }
+  files_.push_back(file);
+}
+
+bool FakeFilesystem::LoadSettings(chromeos::KeyValueStore* store) const {
+  if (minor_version_ < 0)
+    return false;
+  store->SetString("PAYLOAD_MINOR_VERSION", std::to_string(minor_version_));
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/fake_filesystem.h b/payload_generator/fake_filesystem.h
new file mode 100644
index 0000000..35b7756
--- /dev/null
+++ b/payload_generator/fake_filesystem.h
@@ -0,0 +1,56 @@
+// 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_FAKE_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
+
+// A fake filesystem interface implementation allowing the user to add arbitrary
+// files/metadata.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FakeFilesystem : public FilesystemInterface {
+ public:
+  FakeFilesystem(uint64_t block_size, uint64_t block_count);
+  virtual ~FakeFilesystem() = default;
+
+  // FilesystemInterface overrides.
+  size_t GetBlockSize() const override;
+  size_t GetBlockCount() const override;
+  bool GetFiles(std::vector<File>* files) const override;
+  bool LoadSettings(chromeos::KeyValueStore* store) const override;
+
+  // Fake methods.
+
+  // Add a file to the list of fake files.
+  void AddFile(const std::string& filename, const std::vector<Extent> extents);
+
+  // Sets the PAYLOAD_MINOR_VERSION key stored by LoadSettings(). Use a negative
+  // value to produce an error in LoadSettings().
+  void SetMinorVersion(int minor_version) {
+    minor_version_ = minor_version;
+  }
+
+ private:
+  FakeFilesystem() = default;
+
+  uint64_t block_size_;
+  uint64_t block_count_;
+  int minor_version_{-1};
+
+  std::vector<File> files_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeFilesystem);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
diff --git a/payload_generator/filesystem_interface.h b/payload_generator/filesystem_interface.h
new file mode 100644
index 0000000..186eac4
--- /dev/null
+++ b/payload_generator/filesystem_interface.h
@@ -0,0 +1,83 @@
+// 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_FILESYSTEM_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
+
+// This class is used to abstract a filesystem and iterate the blocks
+// associated with the files and filesystem structures.
+// For the purposes of the update payload generation, a filesystem is a formated
+// partition composed by fixed-size blocks, since that's the interface used in
+// the update payload.
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/key_value_store.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FilesystemInterface {
+ public:
+  // This represents a file or pseudo-file in the filesystem. It can include
+  // all sort of files, like symlinks, hardlinks, directories and even a file
+  // entry representing the metadata, free space, journaling data, etc.
+  struct File {
+    File() {
+      memset(&file_stat, 0, sizeof(file_stat));
+    }
+
+    // The stat struct for the file. This is invalid (inode 0) for some
+    // pseudo-files.
+    struct stat file_stat;
+
+    // The absolute path to the file inside the filesystem, for example,
+    // "/usr/bin/bash". For pseudo-files, like blocks associated to internal
+    // filesystem tables or free space, the path doesn't start with a /.
+    std::string name;
+
+    // The list of all physical blocks holding the data of this file in
+    // the same order as the logical data. All the block numbers shall be
+    // between 0 and GetBlockCount() - 1. The blocks are encoded in extents,
+    // indicating the starting block, and the number of consecutive blocks.
+    std::vector<Extent> extents;
+  };
+
+  virtual ~FilesystemInterface() = default;
+
+  // Returns the size of a block in the filesystem.
+  virtual size_t GetBlockSize() const = 0;
+
+  // Returns the number of blocks in the filesystem.
+  virtual size_t GetBlockCount() const = 0;
+
+  // Stores in |files| the list of files and pseudo-files in the filesystem. See
+  // FileInterface for details. The paths returned by this method shall not
+  // be repeated; but the same block could be present in more than one file as
+  // happens for example with hard-linked files, but not limited to those cases.
+  // Returns whether the function succeeded.
+  virtual bool GetFiles(std::vector<File>* files) const = 0;
+
+  // Load the image settings stored in the filesystem in the
+  // /etc/update_engine.conf file. Returns whether the settings were found.
+  virtual bool LoadSettings(chromeos::KeyValueStore* store) const = 0;
+
+ protected:
+  FilesystemInterface() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FilesystemInterface);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
diff --git a/payload_generator/full_update_generator.cc b/payload_generator/full_update_generator.cc
new file mode 100644
index 0000000..c28f7e8
--- /dev/null
+++ b/payload_generator/full_update_generator.cc
@@ -0,0 +1,213 @@
+// Copyright (c) 2012 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/full_update_generator.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include <algorithm>
+#include <deque>
+#include <memory>
+
+#include <base/format_macros.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/utils.h"
+
+using std::deque;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const size_t kDefaultFullChunkSize = 1024 * 1024;  // 1 MiB
+
+// This class encapsulates a full update chunk processing thread. The processor
+// reads a chunk of data from the input file descriptor and compresses it. The
+// processor needs to be started through Start() then waited on through Wait().
+class ChunkProcessor {
+ public:
+  // Read a chunk of |size| bytes from |fd| starting at offset |offset|.
+  ChunkProcessor(int fd, off_t offset, size_t size)
+      : thread_(nullptr),
+        fd_(fd),
+        offset_(offset),
+        buffer_in_(size) {}
+  ~ChunkProcessor() { Wait(); }
+
+  off_t offset() const { return offset_; }
+  const chromeos::Blob& buffer_in() const { return buffer_in_; }
+  const chromeos::Blob& buffer_compressed() const { return buffer_compressed_; }
+
+  // Starts the processor. Returns true on success, false on failure.
+  bool Start();
+
+  // Waits for the processor to complete. Returns true on success, false on
+  // failure.
+  bool Wait();
+
+  bool ShouldCompress() const {
+    return buffer_compressed_.size() < buffer_in_.size();
+  }
+
+ private:
+  // Reads the input data into |buffer_in_| and compresses it into
+  // |buffer_compressed_|. Returns true on success, false otherwise.
+  bool ReadAndCompress();
+  static gpointer ReadAndCompressThread(gpointer data);
+
+  GThread* thread_;
+  int fd_;
+  off_t offset_;
+  chromeos::Blob buffer_in_;
+  chromeos::Blob buffer_compressed_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChunkProcessor);
+};
+
+bool ChunkProcessor::Start() {
+  // g_thread_create is deprecated since glib 2.32. Use
+  // g_thread_new instead.
+  thread_ = g_thread_try_new("chunk_proc", ReadAndCompressThread, this,
+                             nullptr);
+  TEST_AND_RETURN_FALSE(thread_ != nullptr);
+  return true;
+}
+
+bool ChunkProcessor::Wait() {
+  if (!thread_) {
+    return false;
+  }
+  gpointer result = g_thread_join(thread_);
+  thread_ = nullptr;
+  TEST_AND_RETURN_FALSE(result == this);
+  return true;
+}
+
+gpointer ChunkProcessor::ReadAndCompressThread(gpointer data) {
+  return reinterpret_cast<ChunkProcessor*>(data)->ReadAndCompress() ?
+      data : nullptr;
+}
+
+bool ChunkProcessor::ReadAndCompress() {
+  ssize_t bytes_read = -1;
+  TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
+                                        buffer_in_.data(),
+                                        buffer_in_.size(),
+                                        offset_,
+                                        &bytes_read));
+  TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(buffer_in_.size()));
+  TEST_AND_RETURN_FALSE(BzipCompress(buffer_in_, &buffer_compressed_));
+  return true;
+}
+
+}  // namespace
+
+bool FullUpdateGenerator::GenerateOperations(
+    const PayloadGenerationConfig& config,
+    int fd,
+    off_t* data_file_size,
+    vector<AnnotatedOperation>* rootfs_ops,
+    vector<AnnotatedOperation>* kernel_ops) {
+  TEST_AND_RETURN_FALSE(config.Validate());
+  rootfs_ops->clear();
+  kernel_ops->clear();
+
+  // 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) {
+    full_chunk_size = config.chunk_size;
+  } else {
+    LOG(INFO) << "No chunk_size provided, using the default chunk_size for the "
+              << "full operations: " << full_chunk_size << " bytes.";
+  }
+  TEST_AND_RETURN_FALSE(full_chunk_size > 0);
+  TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0);
+
+  size_t max_threads = std::max(sysconf(_SC_NPROCESSORS_ONLN), 4L);
+  LOG(INFO) << "Max threads: " << max_threads;
+
+  const PartitionConfig* partitions[] = {
+      &config.target.rootfs,
+      &config.target.kernel};
+
+  for (int part_id = 0; part_id < 2; ++part_id) {
+    const PartitionConfig* partition = partitions[part_id];
+    LOG(INFO) << "compressing " << partition->path;
+    int in_fd = open(partition->path.c_str(), O_RDONLY, 0);
+    TEST_AND_RETURN_FALSE(in_fd >= 0);
+    ScopedFdCloser in_fd_closer(&in_fd);
+    deque<shared_ptr<ChunkProcessor>> threads;
+    int last_progress_update = INT_MIN;
+    size_t bytes_left = partition->size, counter = 0, offset = 0;
+    while (bytes_left > 0 || !threads.empty()) {
+      // Check and start new chunk processors if possible.
+      while (threads.size() < max_threads && bytes_left > 0) {
+        size_t this_chunk_bytes = std::min(bytes_left, full_chunk_size);
+        shared_ptr<ChunkProcessor> processor(
+            new ChunkProcessor(in_fd, offset, this_chunk_bytes));
+        threads.push_back(processor);
+        TEST_AND_RETURN_FALSE(processor->Start());
+        bytes_left -= this_chunk_bytes;
+        offset += this_chunk_bytes;
+      }
+
+      // Need to wait for a chunk processor to complete and process its output
+      // before spawning new processors.
+      shared_ptr<ChunkProcessor> processor = threads.front();
+      threads.pop_front();
+      TEST_AND_RETURN_FALSE(processor->Wait());
+
+      DeltaArchiveManifest_InstallOperation* op = nullptr;
+      if (part_id == 0) {
+        rootfs_ops->emplace_back();
+        rootfs_ops->back().name =
+            base::StringPrintf("<rootfs-operation-%" PRIuS ">", counter++);
+        op = &rootfs_ops->back().op;
+      } else {
+        kernel_ops->emplace_back();
+        kernel_ops->back().name =
+            base::StringPrintf("<kernel-operation-%" PRIuS ">", counter++);
+        op = &kernel_ops->back().op;
+      }
+
+      const bool compress = processor->ShouldCompress();
+      const chromeos::Blob& use_buf =
+          compress ? processor->buffer_compressed() : processor->buffer_in();
+      op->set_type(compress ?
+                   DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ :
+                   DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+      op->set_data_offset(*data_file_size);
+      TEST_AND_RETURN_FALSE(utils::WriteAll(fd, use_buf.data(),
+                                            use_buf.size()));
+      *data_file_size += use_buf.size();
+      op->set_data_length(use_buf.size());
+      Extent* dst_extent = op->add_dst_extents();
+      dst_extent->set_start_block(processor->offset() / config.block_size);
+      dst_extent->set_num_blocks(
+          processor->buffer_in().size() / config.block_size);
+
+      int progress = static_cast<int>(
+          (processor->offset() + processor->buffer_in().size()) * 100.0 /
+          partition->size);
+      if (last_progress_update < progress &&
+          (last_progress_update + 10 <= progress || progress == 100)) {
+        LOG(INFO) << progress << "% complete (output size: "
+                  << *data_file_size << ")";
+        last_progress_update = progress;
+      }
+    }
+  }
+
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/full_update_generator.h b/payload_generator/full_update_generator.h
new file mode 100644
index 0000000..b4d1135
--- /dev/null
+++ b/payload_generator/full_update_generator.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2010 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_FULL_UPDATE_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#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 : 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 |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.
+  bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      int data_file_fd,
+      off_t* data_file_size,
+      std::vector<AnnotatedOperation>* rootfs_ops,
+      std::vector<AnnotatedOperation>* kernel_ops) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FullUpdateGenerator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
diff --git a/payload_generator/full_update_generator_unittest.cc b/payload_generator/full_update_generator_unittest.cc
new file mode 100644
index 0000000..0a04e4e
--- /dev/null
+++ b/payload_generator/full_update_generator_unittest.cc
@@ -0,0 +1,142 @@
+// Copyright (c) 2010 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/full_update_generator.h"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/test_utils.h"
+
+using chromeos_update_engine::test_utils::FillWithData;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FullUpdateGeneratorTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    config_.is_delta = false;
+    config_.minor_version = DeltaPerformer::kFullPayloadMinorVersion;
+    config_.chunk_size = 128 * 1024;
+    config_.block_size = 4096;
+
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_rootfs.XXXXXX",
+                                    &config_.target.rootfs.path,
+                                    nullptr));
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_kernel.XXXXXX",
+                                    &config_.target.kernel.path,
+                                    nullptr));
+    EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_blobs.XXXXXX",
+                                    &out_blobs_path_,
+                                    &out_blobs_fd_));
+
+    rootfs_part_unlinker_.reset(
+        new ScopedPathUnlinker(config_.target.rootfs.path));
+    kernel_part_unlinker_.reset(
+        new ScopedPathUnlinker(config_.target.kernel.path));
+    out_blobs_unlinker_.reset(new ScopedPathUnlinker(out_blobs_path_));
+  }
+
+  PayloadGenerationConfig config_;
+
+  // Output file holding the payload blobs.
+  string out_blobs_path_;
+  int out_blobs_fd_{-1};
+  ScopedFdCloser out_blobs_fd_closer_{&out_blobs_fd_};
+
+  std::unique_ptr<ScopedPathUnlinker> rootfs_part_unlinker_;
+  std::unique_ptr<ScopedPathUnlinker> kernel_part_unlinker_;
+  std::unique_ptr<ScopedPathUnlinker> out_blobs_unlinker_;
+
+  // FullUpdateGenerator under test.
+  FullUpdateGenerator generator_;
+};
+
+TEST_F(FullUpdateGeneratorTest, RunTest) {
+  chromeos::Blob new_root(9 * 1024 * 1024);
+  chromeos::Blob new_kern(3 * 1024 * 1024);
+  FillWithData(&new_root);
+  FillWithData(&new_kern);
+
+  // Assume hashes take 2 MiB beyond the rootfs.
+  config_.rootfs_partition_size = new_root.size();
+  config_.target.rootfs.size = new_root.size() - 2 * 1024 * 1024;
+  config_.target.kernel.size = new_kern.size();
+
+  EXPECT_TRUE(test_utils::WriteFileVector(config_.target.rootfs.path,
+                                          new_root));
+  EXPECT_TRUE(test_utils::WriteFileVector(config_.target.kernel.path,
+                                          new_kern));
+
+  off_t out_blobs_length = 0;
+  vector<AnnotatedOperation> rootfs_ops;
+  vector<AnnotatedOperation> kernel_ops;
+
+  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_chunks, rootfs_ops.size());
+  EXPECT_EQ(new_kern.size() / config_.chunk_size, kernel_ops.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,
+              rootfs_ops[i].op.dst_extents(0).start_block()) << "i = " << i;
+    EXPECT_EQ(config_.chunk_size / config_.block_size,
+              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,
+                rootfs_ops[i].op.type());
+    }
+  }
+}
+
+// Test that if the chunk size is not a divisor of the image size, it handles
+// correctly the last chunk of each partition.
+TEST_F(FullUpdateGeneratorTest, ChunkSizeTooBig) {
+  config_.chunk_size = 1024 * 1024;
+  chromeos::Blob new_root(1536 * 1024);  // 1.5 MiB
+  chromeos::Blob new_kern(128 * 1024);
+  config_.rootfs_partition_size = new_root.size();
+  config_.target.rootfs.size = new_root.size();
+  config_.target.kernel.size = new_kern.size();
+
+  EXPECT_TRUE(test_utils::WriteFileVector(config_.target.rootfs.path,
+                                          new_root));
+  EXPECT_TRUE(test_utils::WriteFileVector(config_.target.kernel.path,
+                                          new_kern));
+
+  off_t out_blobs_length = 0;
+  vector<AnnotatedOperation> rootfs_ops;
+  vector<AnnotatedOperation> kernel_ops;
+
+  EXPECT_TRUE(generator_.GenerateOperations(config_,
+                                            out_blobs_fd_,
+                                            &out_blobs_length,
+                                            &rootfs_ops,
+                                            &kernel_ops));
+  // rootfs has one chunk and a half.
+  EXPECT_EQ(2, rootfs_ops.size());
+  EXPECT_EQ(config_.chunk_size / config_.block_size,
+            BlocksInExtents(rootfs_ops[0].op.dst_extents()));
+  EXPECT_EQ((new_root.size() - config_.chunk_size) / config_.block_size,
+            BlocksInExtents(rootfs_ops[1].op.dst_extents()));
+
+  // kernel has less than one chunk.
+  EXPECT_EQ(1, kernel_ops.size());
+  EXPECT_EQ(new_kern.size() / config_.block_size,
+            BlocksInExtents(kernel_ops[0].op.dst_extents()));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
new file mode 100644
index 0000000..170a612
--- /dev/null
+++ b/payload_generator/generate_delta_main.cc
@@ -0,0 +1,462 @@
+// Copyright (c) 2010 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 <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <chromeos/flag_helper.h>
+#include <glib.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/prefs.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+// This file contains a simple program that takes an old path, a new path,
+// and an output file as arguments and the path to an output file and
+// generates a delta that can be sent to Chrome OS clients.
+
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+void ParseSignatureSizes(const string& signature_sizes_flag,
+                         vector<int>* signature_sizes) {
+  signature_sizes->clear();
+  vector<string> split_strings;
+
+  base::SplitString(signature_sizes_flag, ':', &split_strings);
+  for (const string& str : split_strings) {
+    int size = 0;
+    bool parsing_successful = base::StringToInt(str, &size);
+    LOG_IF(FATAL, !parsing_successful)
+        << "Invalid signature size: " << str;
+
+    LOG_IF(FATAL, size != (2048 / 8)) <<
+        "Only signature sizes of 256 bytes are supported.";
+
+    signature_sizes->push_back(size);
+  }
+}
+
+bool ParseImageInfo(const string& channel,
+                    const string& board,
+                    const string& version,
+                    const string& key,
+                    const string& build_channel,
+                    const string& build_version,
+                    ImageInfo* image_info) {
+  // All of these arguments should be present or missing.
+  bool empty = channel.empty();
+
+  CHECK_EQ(channel.empty(), empty);
+  CHECK_EQ(board.empty(), empty);
+  CHECK_EQ(version.empty(), empty);
+  CHECK_EQ(key.empty(), empty);
+
+  if (empty)
+    return false;
+
+  image_info->set_channel(channel);
+  image_info->set_board(board);
+  image_info->set_version(version);
+  image_info->set_key(key);
+
+  image_info->set_build_channel(
+      build_channel.empty() ? channel : build_channel);
+
+  image_info->set_build_version(
+      build_version.empty() ? version : build_version);
+
+  return true;
+}
+
+void CalculatePayloadHashForSigning(const vector<int> &sizes,
+                                    const string& out_hash_file,
+                                    const string& in_file) {
+  LOG(INFO) << "Calculating payload hash for signing.";
+  LOG_IF(FATAL, in_file.empty())
+      << "Must pass --in_file to calculate hash for signing.";
+  LOG_IF(FATAL, out_hash_file.empty())
+      << "Must pass --out_hash_file to calculate hash for signing.";
+
+  chromeos::Blob hash;
+  bool result = PayloadSigner::HashPayloadForSigning(in_file, sizes,
+                                                     &hash);
+  CHECK(result);
+
+  result = utils::WriteFile(out_hash_file.c_str(), hash.data(), hash.size());
+  CHECK(result);
+  LOG(INFO) << "Done calculating payload hash for signing.";
+}
+
+
+void CalculateMetadataHashForSigning(const vector<int> &sizes,
+                                     const string& out_metadata_hash_file,
+                                     const string& in_file) {
+  LOG(INFO) << "Calculating metadata hash for signing.";
+  LOG_IF(FATAL, in_file.empty())
+      << "Must pass --in_file to calculate metadata hash for signing.";
+  LOG_IF(FATAL, out_metadata_hash_file.empty())
+      << "Must pass --out_metadata_hash_file to calculate metadata hash.";
+
+  chromeos::Blob hash;
+  bool result = PayloadSigner::HashMetadataForSigning(in_file, sizes,
+                                                      &hash);
+  CHECK(result);
+
+  result = utils::WriteFile(out_metadata_hash_file.c_str(), hash.data(),
+                            hash.size());
+  CHECK(result);
+
+  LOG(INFO) << "Done calculating metadata hash for signing.";
+}
+
+void SignPayload(const string& in_file,
+                 const string& out_file,
+                 const string& signature_file) {
+  LOG(INFO) << "Signing payload.";
+  LOG_IF(FATAL, in_file.empty())
+      << "Must pass --in_file to sign payload.";
+  LOG_IF(FATAL, out_file.empty())
+      << "Must pass --out_file to sign payload.";
+  LOG_IF(FATAL, signature_file.empty())
+      << "Must pass --signature_file to sign payload.";
+  vector<chromeos::Blob> signatures;
+  vector<string> signature_files;
+  base::SplitString(signature_file, ':', &signature_files);
+  for (const string& signature_file : signature_files) {
+    chromeos::Blob signature;
+    CHECK(utils::ReadFile(signature_file, &signature));
+    signatures.push_back(signature);
+  }
+  uint64_t final_metadata_size;
+  CHECK(PayloadSigner::AddSignatureToPayload(
+      in_file, signatures, out_file, &final_metadata_size));
+  LOG(INFO) << "Done signing payload. Final metadata size = "
+            << final_metadata_size;
+}
+
+void VerifySignedPayload(const string& in_file,
+                         const string& public_key,
+                         int public_key_version) {
+  LOG(INFO) << "Verifying signed payload.";
+  LOG_IF(FATAL, in_file.empty())
+      << "Must pass --in_file to verify signed payload.";
+  LOG_IF(FATAL, public_key.empty())
+      << "Must pass --public_key to verify signed payload.";
+  CHECK(PayloadVerifier::VerifySignedPayload(in_file, public_key,
+                                             public_key_version));
+  LOG(INFO) << "Done verifying signed payload.";
+}
+
+void ApplyDelta(const string& in_file,
+                const string& old_kernel,
+                const string& old_rootfs,
+                const string& prefs_dir) {
+  LOG(INFO) << "Applying delta.";
+  LOG_IF(FATAL, old_rootfs.empty())
+      << "Must pass --old_image to apply delta.";
+  Prefs prefs;
+  InstallPlan install_plan;
+  LOG(INFO) << "Setting up preferences under: " << prefs_dir;
+  LOG_IF(ERROR, !prefs.Init(base::FilePath(prefs_dir)))
+      << "Failed to initialize preferences.";
+  // Get original checksums
+  LOG(INFO) << "Calculating original checksums";
+  PartitionInfo kern_info, root_info;
+  ImageConfig old_image;
+  old_image.kernel.path = old_kernel;
+  old_image.rootfs.path = old_rootfs;
+  CHECK(old_image.LoadImageSize());
+  CHECK(diff_utils::InitializePartitionInfo(old_image.kernel, &kern_info));
+  CHECK(diff_utils::InitializePartitionInfo(old_image.rootfs, &root_info));
+  install_plan.kernel_hash.assign(kern_info.hash().begin(),
+                                  kern_info.hash().end());
+  install_plan.rootfs_hash.assign(root_info.hash().begin(),
+                                  root_info.hash().end());
+  DeltaPerformer performer(&prefs, nullptr, &install_plan);
+  CHECK_EQ(performer.Open(old_rootfs.c_str(), 0, 0), 0);
+  CHECK(performer.OpenKernel(old_kernel.c_str()));
+  chromeos::Blob buf(1024 * 1024);
+  int fd = open(in_file.c_str(), O_RDONLY, 0);
+  CHECK_GE(fd, 0);
+  ScopedFdCloser fd_closer(&fd);
+  for (off_t offset = 0;; offset += buf.size()) {
+    ssize_t bytes_read;
+    CHECK(utils::PReadAll(fd, buf.data(), buf.size(), offset, &bytes_read));
+    if (bytes_read == 0)
+      break;
+    CHECK_EQ(performer.Write(buf.data(), bytes_read), bytes_read);
+  }
+  CHECK_EQ(performer.Close(), 0);
+  DeltaPerformer::ResetUpdateProgress(&prefs, false);
+  LOG(INFO) << "Done applying delta.";
+}
+
+int Main(int argc, char** argv) {
+  DEFINE_string(old_dir, "",
+                "[DEPRECATED] Directory where the old rootfs is loop mounted "
+                "read-only. Not required anymore.");
+  DEFINE_string(new_dir, "",
+                "[DEPRECATED] Directory where the new rootfs is loop mounted "
+                "read-only. Not required anymore.");
+  DEFINE_string(old_image, "", "Path to the old rootfs");
+  DEFINE_string(new_image, "", "Path to the new rootfs");
+  DEFINE_string(old_kernel, "", "Path to the old kernel partition image");
+  DEFINE_string(new_kernel, "", "Path to the new kernel partition image");
+  DEFINE_string(in_file, "",
+                "Path to input delta payload file used to hash/sign payloads "
+                "and apply delta over old_image (for debugging)");
+  DEFINE_string(out_file, "", "Path to output delta payload file");
+  DEFINE_string(out_hash_file, "", "Path to output hash file");
+  DEFINE_string(out_metadata_hash_file, "",
+                "Path to output metadata hash file");
+  DEFINE_string(private_key, "", "Path to private key in .pem format");
+  DEFINE_string(public_key, "", "Path to public key in .pem format");
+  DEFINE_int32(public_key_version,
+               chromeos_update_engine::kSignatureMessageCurrentVersion,
+               "Key-check version # of client");
+  DEFINE_string(prefs_dir, "/tmp/update_engine_prefs",
+                "Preferences directory, used with apply_delta");
+  DEFINE_string(signature_size, "",
+                "Raw signature size used for hash calculation. "
+                "You may pass in multiple sizes by colon separating them. E.g. "
+                "2048:2048:4096 will assume 3 signatures, the first two with "
+                "2048 size and the last 4096.");
+  DEFINE_string(signature_file, "",
+                "Raw signature file to sign payload with. To pass multiple "
+                "signatures, use a single argument with a colon between paths, "
+                "e.g. /path/to/sig:/path/to/next:/path/to/last_sig . Each "
+                "signature will be assigned a client version, starting from "
+                "kSignatureOriginalVersion.");
+  DEFINE_int32(chunk_size, -1, "Payload chunk size (-1 -- no limit/default)");
+  DEFINE_uint64(rootfs_partition_size,
+               chromeos_update_engine::kRootFSPartitionSize,
+               "RootFS partition size for the image once installed");
+  DEFINE_int32(minor_version, -1,
+               "The minor version of the payload being generated "
+               "(-1 means autodetect).");
+
+  DEFINE_string(old_channel, "",
+                "The channel for the old image. 'dev-channel', 'npo-channel', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(old_board, "",
+                "The board for the old image. 'x86-mario', 'lumpy', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(old_version, "",
+                "The build version of the old image. 1.2.3, etc.");
+  DEFINE_string(old_key, "",
+                "The key used to sign the old image. 'premp', 'mp', 'mp-v3',"
+                " etc");
+  DEFINE_string(old_build_channel, "",
+                "The channel for the build of the old image. 'dev-channel', "
+                "etc, but will never contain special channels such as "
+                "'npo-channel'. Ignored, except during delta generation.");
+  DEFINE_string(old_build_version, "",
+                "The version of the build containing the old image.");
+
+  DEFINE_string(new_channel, "",
+                "The channel for the new image. 'dev-channel', 'npo-channel', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(new_board, "",
+                "The board for the new image. 'x86-mario', 'lumpy', "
+                "etc. Ignored, except during delta generation.");
+  DEFINE_string(new_version, "",
+                "The build version of the new image. 1.2.3, etc.");
+  DEFINE_string(new_key, "",
+                "The key used to sign the new image. 'premp', 'mp', 'mp-v3',"
+                " etc");
+  DEFINE_string(new_build_channel, "",
+                "The channel for the build of the new image. 'dev-channel', "
+                "etc, but will never contain special channels such as "
+                "'npo-channel'. Ignored, except during delta generation.");
+  DEFINE_string(new_build_version, "",
+                "The version of the build containing the new image.");
+
+  chromeos::FlagHelper::Init(argc, argv,
+      "Generates a payload to provide to ChromeOS' update_engine.\n\n"
+      "This tool can create full payloads and also delta payloads if the src\n"
+      "image is provided. It also provides debugging options to apply, sign\n"
+      "and verify payloads.");
+  Terminator::Init();
+  Subprocess::Init();
+
+  logging::LoggingSettings log_settings;
+  log_settings.log_file     = "delta_generator.log";
+  log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+  log_settings.lock_log     = logging::DONT_LOCK_LOG_FILE;
+  log_settings.delete_old   = logging::APPEND_TO_OLD_LOG_FILE;
+
+  logging::InitLogging(log_settings);
+
+  // Check flags.
+  if (!FLAGS_old_dir.empty()) {
+    LOG(INFO) << "--old_dir flag is deprecated and ignored.";
+  }
+  if (!FLAGS_new_dir.empty()) {
+    LOG(INFO) << "--new_dir flag is deprecated and ignored.";
+  }
+
+  vector<int> signature_sizes;
+  ParseSignatureSizes(FLAGS_signature_size, &signature_sizes);
+
+  if (!FLAGS_out_hash_file.empty() || !FLAGS_out_metadata_hash_file.empty()) {
+    if (!FLAGS_out_hash_file.empty()) {
+      CalculatePayloadHashForSigning(signature_sizes, FLAGS_out_hash_file,
+                                     FLAGS_in_file);
+    }
+    if (!FLAGS_out_metadata_hash_file.empty()) {
+      CalculateMetadataHashForSigning(signature_sizes,
+                                      FLAGS_out_metadata_hash_file,
+                                      FLAGS_in_file);
+    }
+    return 0;
+  }
+  if (!FLAGS_signature_file.empty()) {
+    SignPayload(FLAGS_in_file, FLAGS_out_file, FLAGS_signature_file);
+    return 0;
+  }
+  if (!FLAGS_public_key.empty()) {
+    VerifySignedPayload(FLAGS_in_file, FLAGS_public_key,
+                        FLAGS_public_key_version);
+    return 0;
+  }
+  if (!FLAGS_in_file.empty()) {
+    ApplyDelta(FLAGS_in_file, FLAGS_old_kernel, FLAGS_old_image,
+               FLAGS_prefs_dir);
+    return 0;
+  }
+
+  // A payload generation was requested. Convert the flags to a
+  // PayloadGenerationConfig.
+  PayloadGenerationConfig payload_config;
+  payload_config.source.rootfs.path = FLAGS_old_image;
+  payload_config.source.kernel.path = FLAGS_old_kernel;
+
+  payload_config.target.rootfs.path = FLAGS_new_image;
+  payload_config.target.kernel.path = FLAGS_new_kernel;
+
+  payload_config.chunk_size = FLAGS_chunk_size;
+  payload_config.block_size = kBlockSize;
+
+  // The kernel and rootfs size is never passed to the delta_generator, so we
+  // need to detect those from the provided files.
+  if (!FLAGS_old_image.empty()) {
+    CHECK(payload_config.source.LoadImageSize());
+  }
+  if (!FLAGS_new_image.empty()) {
+    CHECK(payload_config.target.LoadImageSize());
+  }
+
+  payload_config.is_delta = !FLAGS_old_image.empty();
+
+  CHECK(!FLAGS_out_file.empty());
+
+  // Ignore failures. These are optional arguments.
+  ParseImageInfo(FLAGS_new_channel,
+                 FLAGS_new_board,
+                 FLAGS_new_version,
+                 FLAGS_new_key,
+                 FLAGS_new_build_channel,
+                 FLAGS_new_build_version,
+                 &payload_config.target.image_info);
+
+  // Ignore failures. These are optional arguments.
+  ParseImageInfo(FLAGS_old_channel,
+                 FLAGS_old_board,
+                 FLAGS_old_version,
+                 FLAGS_old_key,
+                 FLAGS_old_build_channel,
+                 FLAGS_old_build_version,
+                 &payload_config.source.image_info);
+
+  payload_config.rootfs_partition_size = FLAGS_rootfs_partition_size;
+
+  // Load the rootfs size from verity's kernel command line if rootfs
+  // verification is enabled.
+  payload_config.source.LoadVerityRootfsSize();
+  payload_config.target.LoadVerityRootfsSize();
+
+  if (payload_config.is_delta) {
+    // Avoid opening the filesystem interface for full payloads.
+    CHECK(payload_config.target.rootfs.OpenFilesystem());
+    CHECK(payload_config.target.kernel.OpenFilesystem());
+    CHECK(payload_config.source.rootfs.OpenFilesystem());
+    CHECK(payload_config.source.kernel.OpenFilesystem());
+  }
+
+  if (FLAGS_minor_version == -1) {
+    // Autodetect minor_version by looking at the update_engine.conf in the old
+    // image.
+    if (payload_config.is_delta) {
+      CHECK(payload_config.source.rootfs.fs_interface);
+      chromeos::KeyValueStore store;
+      uint32_t minor_version;
+      if (payload_config.source.rootfs.fs_interface->LoadSettings(&store) &&
+          utils::GetMinorVersion(store, &minor_version)) {
+        payload_config.minor_version = minor_version;
+      } else {
+        payload_config.minor_version = kInPlaceMinorPayloadVersion;
+      }
+    } else {
+      payload_config.minor_version = DeltaPerformer::kFullPayloadMinorVersion;
+    }
+    LOG(INFO) << "Auto-detected minor_version=" << payload_config.minor_version;
+  } else {
+    payload_config.minor_version = FLAGS_minor_version;
+    LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
+  }
+
+  if (payload_config.is_delta) {
+    LOG(INFO) << "Generating delta update";
+  } else {
+    LOG(INFO) << "Generating full update";
+  }
+
+  // From this point, all the options have been parsed.
+  if (!payload_config.Validate()) {
+    LOG(ERROR) << "Invalid options passed. See errors above.";
+    return 1;
+  }
+
+  uint64_t metadata_size;
+  if (!GenerateUpdatePayloadFile(payload_config,
+                                 FLAGS_out_file,
+                                 FLAGS_private_key,
+                                 &metadata_size)) {
+    return 1;
+  }
+
+  return 0;
+}
+
+}  // namespace
+
+}  // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+  return chromeos_update_engine::Main(argc, argv);
+}
diff --git a/payload_generator/graph_types.cc b/payload_generator/graph_types.cc
new file mode 100644
index 0000000..3e5adc6
--- /dev/null
+++ b/payload_generator/graph_types.cc
@@ -0,0 +1,11 @@
+// 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/graph_types.h"
+
+namespace chromeos_update_engine {
+
+const Vertex::Index Vertex::kInvalidIndex = static_cast<Vertex::Index>(-1);
+
+}  // chromeos_update_engine
diff --git a/payload_generator/graph_types.h b/payload_generator/graph_types.h
new file mode 100644
index 0000000..7b20bb5
--- /dev/null
+++ b/payload_generator/graph_types.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2009 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_GRAPH_TYPES_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+// A few classes that help in generating delta images use these types
+// for the graph work.
+
+namespace chromeos_update_engine {
+
+struct EdgeProperties {
+  // Read-before extents. I.e., blocks in |extents| must be read by the
+  // node pointed to before the pointing node runs (presumably b/c it
+  // overwrites these blocks).
+  std::vector<Extent> extents;
+
+  // Write before extents. I.e., blocks in |write_extents| must be written
+  // by the node pointed to before the pointing node runs (presumably
+  // b/c it reads the data written by the other node).
+  std::vector<Extent> write_extents;
+
+  bool operator==(const EdgeProperties& that) const {
+    return extents == that.extents && write_extents == that.write_extents;
+  }
+};
+
+struct Vertex {
+  Vertex() :
+      valid(true),
+      index(-1),
+      lowlink(-1) {}
+  bool valid;
+
+  typedef std::map<std::vector<Vertex>::size_type, EdgeProperties> EdgeMap;
+  EdgeMap out_edges;
+
+  // We sometimes wish to consider a subgraph of a graph. A subgraph would have
+  // a subset of the vertices from the graph and a subset of the edges.
+  // When considering this vertex within a subgraph, subgraph_edges stores
+  // the out-edges.
+  typedef std::set<std::vector<Vertex>::size_type> SubgraphEdgeMap;
+  SubgraphEdgeMap subgraph_edges;
+
+  // For Tarjan's algorithm:
+  std::vector<Vertex>::size_type index;
+  std::vector<Vertex>::size_type lowlink;
+
+  // Other Vertex properties:
+  DeltaArchiveManifest_InstallOperation op;
+  std::string file_name;
+
+  typedef std::vector<Vertex>::size_type Index;
+  static const Vertex::Index kInvalidIndex;
+};
+
+typedef std::vector<Vertex> Graph;
+
+typedef std::pair<Vertex::Index, Vertex::Index> Edge;
+
+const uint64_t kTempBlockStart = 1ULL << 60;
+COMPILE_ASSERT(kTempBlockStart != 0, kTempBlockStart_invalid);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
diff --git a/payload_generator/graph_utils.cc b/payload_generator/graph_utils.cc
new file mode 100644
index 0000000..9166560
--- /dev/null
+++ b/payload_generator/graph_utils.cc
@@ -0,0 +1,129 @@
+// Copyright (c) 2009 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/graph_utils.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+namespace graph_utils {
+
+uint64_t EdgeWeight(const Graph& graph, const Edge& edge) {
+  uint64_t weight = 0;
+  const vector<Extent>& extents =
+      graph[edge.first].out_edges.find(edge.second)->second.extents;
+  for (vector<Extent>::const_iterator it = extents.begin();
+       it != extents.end(); ++it) {
+    if (it->start_block() != kSparseHole)
+      weight += it->num_blocks();
+  }
+  return weight;
+}
+
+void AddReadBeforeDep(Vertex* src,
+                      Vertex::Index dst,
+                      uint64_t block) {
+  Vertex::EdgeMap::iterator edge_it = src->out_edges.find(dst);
+  if (edge_it == src->out_edges.end()) {
+    // Must create new edge
+    pair<Vertex::EdgeMap::iterator, bool> result =
+        src->out_edges.insert(make_pair(dst, EdgeProperties()));
+    CHECK(result.second);
+    edge_it = result.first;
+  }
+  AppendBlockToExtents(&edge_it->second.extents, block);
+}
+
+void AddReadBeforeDepExtents(Vertex* src,
+                             Vertex::Index dst,
+                             const vector<Extent>& extents) {
+  // TODO(adlr): Be more efficient than adding each block individually.
+  for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+       it != e; ++it) {
+    const Extent& extent = *it;
+    for (uint64_t block = extent.start_block(),
+             block_end = extent.start_block() + extent.num_blocks();
+         block != block_end; ++block) {
+      AddReadBeforeDep(src, dst, block);
+    }
+  }
+}
+
+void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map) {
+  // Specially crafted for-loop for the map-iterate-delete dance.
+  for (Vertex::EdgeMap::iterator it = edge_map->begin();
+       it != edge_map->end(); ) {
+    if (!it->second.write_extents.empty())
+      it->second.write_extents.clear();
+    if (it->second.extents.empty()) {
+      // Erase *it, as it contains no blocks
+      edge_map->erase(it++);
+    } else {
+      ++it;
+    }
+  }
+}
+
+// For each node N in graph, drop all edges N->|index|.
+void DropIncomingEdgesTo(Graph* graph, Vertex::Index index) {
+  // This would be much more efficient if we had doubly-linked
+  // edges in the graph.
+  for (Graph::iterator it = graph->begin(), e = graph->end(); it != e; ++it) {
+    it->out_edges.erase(index);
+  }
+}
+
+namespace {
+template<typename T>
+void DumpExtents(const T& field, int prepend_space_count) {
+  string header(prepend_space_count, ' ');
+  for (int i = 0, e = field.size(); i != e; ++i) {
+    LOG(INFO) << header << "(" << GetElement(field, i).start_block() << ", "
+              << GetElement(field, i).num_blocks() << ")";
+  }
+}
+
+void DumpOutEdges(const Vertex::EdgeMap& out_edges) {
+  for (Vertex::EdgeMap::const_iterator it = out_edges.begin(),
+           e = out_edges.end(); it != e; ++it) {
+    LOG(INFO) << "    " << it->first << " read-before:";
+    DumpExtents(it->second.extents, 6);
+    LOG(INFO) << "      write-before:";
+    DumpExtents(it->second.write_extents, 6);
+  }
+}
+}  // namespace
+
+void DumpGraph(const Graph& graph) {
+  LOG(INFO) << "Graph length: " << graph.size();
+  for (Graph::size_type i = 0, e = graph.size(); i != e; ++i) {
+    LOG(INFO) << i
+              << (graph[i].valid ? "" : "-INV")
+              << ": " << graph[i].file_name
+              << ": " << InstallOperationTypeName(graph[i].op.type());
+    LOG(INFO) << "  src_extents:";
+    DumpExtents(graph[i].op.src_extents(), 4);
+    LOG(INFO) << "  dst_extents:";
+    DumpExtents(graph[i].op.dst_extents(), 4);
+    LOG(INFO) << "  out edges:";
+    DumpOutEdges(graph[i].out_edges);
+  }
+}
+
+}  // namespace graph_utils
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/graph_utils.h b/payload_generator/graph_utils.h
new file mode 100644
index 0000000..6595e57
--- /dev/null
+++ b/payload_generator/graph_utils.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2009 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_GRAPH_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
+
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/update_metadata.pb.h"
+
+// A few utility functions for graphs
+
+namespace chromeos_update_engine {
+
+namespace graph_utils {
+
+// Returns the number of blocks represented by all extents in the edge.
+uint64_t EdgeWeight(const Graph& graph, const Edge& edge);
+
+// These add a read-before dependency from graph[src] -> graph[dst]. If the dep
+// already exists, the block/s is/are added to the existing edge.
+void AddReadBeforeDep(Vertex* src,
+                      Vertex::Index dst,
+                      uint64_t block);
+void AddReadBeforeDepExtents(Vertex* src,
+                             Vertex::Index dst,
+                             const std::vector<Extent>& extents);
+
+void DropWriteBeforeDeps(Vertex::EdgeMap* edge_map);
+
+// For each node N in graph, drop all edges N->|index|.
+void DropIncomingEdgesTo(Graph* graph, Vertex::Index index);
+
+void DumpGraph(const Graph& graph);
+
+}  // namespace graph_utils
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_UTILS_H_
diff --git a/payload_generator/graph_utils_unittest.cc b/payload_generator/graph_utils_unittest.cc
new file mode 100644
index 0000000..bd6c979
--- /dev/null
+++ b/payload_generator/graph_utils_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2009 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/graph_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::make_pair;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class GraphUtilsTest : public ::testing::Test {};
+
+TEST(GraphUtilsTest, SimpleTest) {
+  Graph graph(2);
+
+  graph[0].out_edges.insert(make_pair(1, EdgeProperties()));
+
+  vector<Extent>& extents = graph[0].out_edges[1].extents;
+
+  EXPECT_EQ(0, extents.size());
+  AppendBlockToExtents(&extents, 0);
+  EXPECT_EQ(1, extents.size());
+  AppendBlockToExtents(&extents, 1);
+  AppendBlockToExtents(&extents, 2);
+  EXPECT_EQ(1, extents.size());
+  AppendBlockToExtents(&extents, 4);
+
+  EXPECT_EQ(2, extents.size());
+  EXPECT_EQ(0, extents[0].start_block());
+  EXPECT_EQ(3, extents[0].num_blocks());
+  EXPECT_EQ(4, extents[1].start_block());
+  EXPECT_EQ(1, extents[1].num_blocks());
+
+  EXPECT_EQ(4, graph_utils::EdgeWeight(graph, make_pair(0, 1)));
+}
+
+
+TEST(GraphUtilsTest, DepsTest) {
+  Graph graph(3);
+
+  graph_utils::AddReadBeforeDep(&graph[0], 1, 3);
+  EXPECT_EQ(1, graph[0].out_edges.size());
+  {
+    Extent& extent = graph[0].out_edges[1].extents[0];
+    EXPECT_EQ(3, extent.start_block());
+    EXPECT_EQ(1, extent.num_blocks());
+  }
+  graph_utils::AddReadBeforeDep(&graph[0], 1, 4);
+  EXPECT_EQ(1, graph[0].out_edges.size());
+  {
+    Extent& extent = graph[0].out_edges[1].extents[0];
+    EXPECT_EQ(3, extent.start_block());
+    EXPECT_EQ(2, extent.num_blocks());
+  }
+  graph_utils::AddReadBeforeDepExtents(&graph[2], 1,
+    vector<Extent>(1, ExtentForRange(5, 2)));
+  EXPECT_EQ(1, graph[2].out_edges.size());
+  {
+    Extent& extent = graph[2].out_edges[1].extents[0];
+    EXPECT_EQ(5, extent.start_block());
+    EXPECT_EQ(2, extent.num_blocks());
+  }
+  // Change most recent edge from read-before to write-before
+  graph[2].out_edges[1].write_extents.swap(graph[2].out_edges[1].extents);
+  graph_utils::DropWriteBeforeDeps(&graph[2].out_edges);
+  EXPECT_EQ(0, graph[2].out_edges.size());
+
+  EXPECT_EQ(1, graph[0].out_edges.size());
+  graph_utils::DropIncomingEdgesTo(&graph, 1);
+  EXPECT_EQ(0, graph[0].out_edges.size());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/inplace_generator.cc b/payload_generator/inplace_generator.cc
new file mode 100644
index 0000000..2e0dbaf
--- /dev/null
+++ b/payload_generator/inplace_generator.cc
@@ -0,0 +1,806 @@
+// 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/inplace_generator.h"
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/cycle_breaker.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/extent_ranges.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/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::map;
+using std::pair;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+using Block = InplaceGenerator::Block;
+
+// This class allocates non-existent temp blocks, starting from
+// kTempBlockStart. Other code is responsible for converting these
+// temp blocks into real blocks, as the client can't read or write to
+// these blocks.
+class DummyExtentAllocator {
+ public:
+  vector<Extent> Allocate(const uint64_t block_count) {
+    vector<Extent> ret(1);
+    ret[0].set_start_block(next_block_);
+    ret[0].set_num_blocks(block_count);
+    next_block_ += block_count;
+    return ret;
+  }
+
+ private:
+  uint64_t next_block_{kTempBlockStart};
+};
+
+// Takes a vector of blocks and returns an equivalent vector of Extent
+// objects.
+vector<Extent> CompressExtents(const vector<uint64_t>& blocks) {
+  vector<Extent> new_extents;
+  for (uint64_t block : blocks) {
+    AppendBlockToExtents(&new_extents, block);
+  }
+  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,
+    const vector<Extent>& replace_extents) {
+  // First, expand out the blocks that op reads from
+  vector<uint64_t> read_blocks =
+      ExpandExtents(vertex->op.src_extents());
+  {
+    // Expand remove_extents and replace_extents
+    vector<uint64_t> remove_extents_expanded = ExpandExtents(remove_extents);
+    vector<uint64_t> replace_extents_expanded = ExpandExtents(replace_extents);
+    CHECK_EQ(remove_extents_expanded.size(), replace_extents_expanded.size());
+    map<uint64_t, uint64_t> conversion;
+    for (vector<uint64_t>::size_type i = 0;
+         i < replace_extents_expanded.size(); i++) {
+      conversion[remove_extents_expanded[i]] = replace_extents_expanded[i];
+    }
+    ApplyMap(&read_blocks, conversion);
+    for (auto& edge_prop_pair : vertex->out_edges) {
+      vector<uint64_t> write_before_deps_expanded = ExpandExtents(
+          edge_prop_pair.second.write_extents);
+      ApplyMap(&write_before_deps_expanded, conversion);
+      edge_prop_pair.second.write_extents =
+          CompressExtents(write_before_deps_expanded);
+    }
+  }
+  // Convert read_blocks back to extents
+  vertex->op.clear_src_extents();
+  vector<Extent> new_extents = CompressExtents(read_blocks);
+  StoreExtents(new_extents, vertex->op.mutable_src_extents());
+}
+
+bool InplaceGenerator::CutEdges(Graph* graph,
+                                const set<Edge>& edges,
+                                vector<CutEdgeVertexes>* out_cuts) {
+  DummyExtentAllocator scratch_allocator;
+  vector<CutEdgeVertexes> cuts;
+  cuts.reserve(edges.size());
+
+  uint64_t scratch_blocks_used = 0;
+  for (const Edge& edge : edges) {
+    cuts.resize(cuts.size() + 1);
+    vector<Extent> old_extents =
+        (*graph)[edge.first].out_edges[edge.second].extents;
+    // Choose some scratch space
+    scratch_blocks_used += graph_utils::EdgeWeight(*graph, edge);
+    cuts.back().tmp_extents =
+        scratch_allocator.Allocate(graph_utils::EdgeWeight(*graph, edge));
+    // create vertex to copy original->scratch
+    cuts.back().new_vertex = graph->size();
+    graph->emplace_back();
+    cuts.back().old_src = edge.first;
+    cuts.back().old_dst = edge.second;
+
+    EdgeProperties& cut_edge_properties =
+        (*graph)[edge.first].out_edges.find(edge.second)->second;
+
+    // This should never happen, as we should only be cutting edges between
+    // real file nodes, and write-before relationships are created from
+    // a real file node to a temp copy node:
+    CHECK(cut_edge_properties.write_extents.empty())
+        << "Can't cut edge that has write-before relationship.";
+
+    // make node depend on the copy operation
+    (*graph)[edge.first].out_edges.insert(make_pair(graph->size() - 1,
+                                                    cut_edge_properties));
+
+    // Set src/dst extents and other proto variables for copy operation
+    graph->back().op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+    StoreExtents(cut_edge_properties.extents,
+                 graph->back().op.mutable_src_extents());
+    StoreExtents(cuts.back().tmp_extents,
+                 graph->back().op.mutable_dst_extents());
+    graph->back().op.set_src_length(
+        graph_utils::EdgeWeight(*graph, edge) * kBlockSize);
+    graph->back().op.set_dst_length(graph->back().op.src_length());
+
+    // make the dest node read from the scratch space
+    SubstituteBlocks(
+        &((*graph)[edge.second]),
+        (*graph)[edge.first].out_edges[edge.second].extents,
+        cuts.back().tmp_extents);
+
+    // delete the old edge
+    CHECK_EQ(static_cast<Graph::size_type>(1),
+             (*graph)[edge.first].out_edges.erase(edge.second));
+
+    // Add an edge from dst to copy operation
+    EdgeProperties write_before_edge_properties;
+    write_before_edge_properties.write_extents = cuts.back().tmp_extents;
+    (*graph)[edge.second].out_edges.insert(
+        make_pair(graph->size() - 1, write_before_edge_properties));
+  }
+  out_cuts->swap(cuts);
+  return true;
+}
+
+// Creates all the edges for the graph. Writers of a block point to
+// readers of the same block. This is because for an edge A->B, B
+// must complete before A executes.
+void InplaceGenerator::CreateEdges(
+    Graph* graph,
+    const vector<Block>& blocks) {
+  for (vector<Block>::size_type i = 0;
+       i < blocks.size(); i++) {
+    // Blocks with both a reader and writer get an edge
+    if (blocks[i].reader == Vertex::kInvalidIndex ||
+        blocks[i].writer == Vertex::kInvalidIndex)
+      continue;
+    // Don't have a node depend on itself
+    if (blocks[i].reader == blocks[i].writer)
+      continue;
+    // See if there's already an edge we can add onto
+    Vertex::EdgeMap::iterator edge_it =
+        (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader);
+    if (edge_it == (*graph)[blocks[i].writer].out_edges.end()) {
+      // No existing edge. Create one
+      (*graph)[blocks[i].writer].out_edges.insert(
+          make_pair(blocks[i].reader, EdgeProperties()));
+      edge_it = (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader);
+      CHECK(edge_it != (*graph)[blocks[i].writer].out_edges.end());
+    }
+    AppendBlockToExtents(&edge_it->second.extents, i);
+  }
+}
+
+namespace {
+
+class SortCutsByTopoOrderLess {
+ public:
+  explicit SortCutsByTopoOrderLess(
+      const vector<vector<Vertex::Index>::size_type>& table)
+      : table_(table) {}
+  bool operator()(const CutEdgeVertexes& a, const CutEdgeVertexes& b) {
+    return table_[a.old_dst] < table_[b.old_dst];
+  }
+ private:
+  const vector<vector<Vertex::Index>::size_type>& table_;
+};
+
+}  // namespace
+
+void InplaceGenerator::GenerateReverseTopoOrderMap(
+    const vector<Vertex::Index>& op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes) {
+  vector<vector<Vertex::Index>::size_type> table(op_indexes.size());
+  for (vector<Vertex::Index>::size_type i = 0, e = op_indexes.size();
+       i != e; ++i) {
+    Vertex::Index node = op_indexes[i];
+    if (table.size() < (node + 1)) {
+      table.resize(node + 1);
+    }
+    table[node] = i;
+  }
+  reverse_op_indexes->swap(table);
+}
+
+void InplaceGenerator::SortCutsByTopoOrder(
+    const vector<Vertex::Index>& op_indexes,
+    vector<CutEdgeVertexes>* cuts) {
+  // first, make a reverse lookup table.
+  vector<vector<Vertex::Index>::size_type> table;
+  GenerateReverseTopoOrderMap(op_indexes, &table);
+  SortCutsByTopoOrderLess less(table);
+  sort(cuts->begin(), cuts->end(), less);
+}
+
+void InplaceGenerator::MoveFullOpsToBack(Graph* graph,
+                                         vector<Vertex::Index>* op_indexes) {
+  vector<Vertex::Index> ret;
+  vector<Vertex::Index> full_ops;
+  ret.reserve(op_indexes->size());
+  for (vector<Vertex::Index>::size_type i = 0, e = op_indexes->size(); i != e;
+       ++i) {
+    DeltaArchiveManifest_InstallOperation_Type type =
+        (*graph)[(*op_indexes)[i]].op.type();
+    if (type == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
+        type == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
+      full_ops.push_back((*op_indexes)[i]);
+    } else {
+      ret.push_back((*op_indexes)[i]);
+    }
+  }
+  LOG(INFO) << "Stats: " << full_ops.size() << " full ops out of "
+            << (full_ops.size() + ret.size()) << " total ops.";
+  ret.insert(ret.end(), full_ops.begin(), full_ops.end());
+  op_indexes->swap(ret);
+}
+
+namespace {
+
+template<typename T>
+bool TempBlocksExistInExtents(const T& extents) {
+  for (int i = 0, e = extents.size(); i < e; ++i) {
+    Extent extent = GetElement(extents, i);
+    uint64_t start = extent.start_block();
+    uint64_t num = extent.num_blocks();
+    if (start >= kTempBlockStart || (start + num) >= kTempBlockStart) {
+      LOG(ERROR) << "temp block!";
+      LOG(ERROR) << "start: " << start << ", num: " << num;
+      LOG(ERROR) << "kTempBlockStart: " << kTempBlockStart;
+      LOG(ERROR) << "returning true";
+      return true;
+    }
+    // check for wrap-around, which would be a bug:
+    CHECK(start <= (start + num));
+  }
+  return false;
+}
+
+// Converts the cuts, which must all have the same |old_dst| member,
+// to full. It does this by converting the |old_dst| to REPLACE or
+// REPLACE_BZ, dropping all incoming edges to |old_dst|, and marking
+// all temp nodes invalid.
+bool ConvertCutsToFull(
+    Graph* graph,
+    const string& new_part,
+    int data_fd,
+    off_t* data_file_size,
+    vector<Vertex::Index>* op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+    const vector<CutEdgeVertexes>& cuts) {
+  CHECK(!cuts.empty());
+  set<Vertex::Index> deleted_nodes;
+  for (const CutEdgeVertexes& cut : cuts) {
+    TEST_AND_RETURN_FALSE(InplaceGenerator::ConvertCutToFullOp(
+        graph,
+        cut,
+        new_part,
+        data_fd,
+        data_file_size));
+    deleted_nodes.insert(cut.new_vertex);
+  }
+  deleted_nodes.insert(cuts[0].old_dst);
+
+  vector<Vertex::Index> new_op_indexes;
+  new_op_indexes.reserve(op_indexes->size());
+  for (Vertex::Index vertex_index : *op_indexes) {
+    if (utils::SetContainsKey(deleted_nodes, vertex_index))
+      continue;
+    new_op_indexes.push_back(vertex_index);
+  }
+  new_op_indexes.push_back(cuts[0].old_dst);
+  op_indexes->swap(new_op_indexes);
+  InplaceGenerator::GenerateReverseTopoOrderMap(*op_indexes,
+                                                reverse_op_indexes);
+  return true;
+}
+
+// Tries to assign temp blocks for a collection of cuts, all of which share
+// the same old_dst member. If temp blocks can't be found, old_dst will be
+// converted to a REPLACE or REPLACE_BZ operation. Returns true on success,
+// which can happen even if blocks are converted to full. Returns false
+// on exceptional error cases.
+bool AssignBlockForAdjoiningCuts(
+    Graph* graph,
+    const string& new_part,
+    int data_fd,
+    off_t* data_file_size,
+    vector<Vertex::Index>* op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+    const vector<CutEdgeVertexes>& cuts) {
+  CHECK(!cuts.empty());
+  const Vertex::Index old_dst = cuts[0].old_dst;
+  // Calculate # of blocks needed
+  uint64_t blocks_needed = 0;
+  vector<uint64_t> cuts_blocks_needed(cuts.size());
+  for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) {
+    uint64_t cut_blocks_needed = 0;
+    for (const Extent& extent : cuts[i].tmp_extents) {
+      cut_blocks_needed += extent.num_blocks();
+    }
+    blocks_needed += cut_blocks_needed;
+    cuts_blocks_needed[i] = cut_blocks_needed;
+  }
+
+  // Find enough blocks
+  ExtentRanges scratch_ranges;
+  // Each block that's supplying temp blocks and the corresponding blocks:
+  typedef vector<pair<Vertex::Index, ExtentRanges>> SupplierVector;
+  SupplierVector block_suppliers;
+  uint64_t scratch_blocks_found = 0;
+  for (vector<Vertex::Index>::size_type i = (*reverse_op_indexes)[old_dst] + 1,
+           e = op_indexes->size(); i < e; ++i) {
+    Vertex::Index test_node = (*op_indexes)[i];
+    if (!(*graph)[test_node].valid)
+      continue;
+    // See if this node has sufficient blocks
+    ExtentRanges ranges;
+    ranges.AddRepeatedExtents((*graph)[test_node].op.dst_extents());
+    ranges.SubtractExtent(ExtentForRange(
+        kTempBlockStart, kSparseHole - kTempBlockStart));
+    ranges.SubtractRepeatedExtents((*graph)[test_node].op.src_extents());
+    // For now, for simplicity, subtract out all blocks in read-before
+    // dependencies.
+    for (Vertex::EdgeMap::const_iterator edge_i =
+             (*graph)[test_node].out_edges.begin(),
+             edge_e = (*graph)[test_node].out_edges.end();
+         edge_i != edge_e; ++edge_i) {
+      ranges.SubtractExtents(edge_i->second.extents);
+    }
+    if (ranges.blocks() == 0)
+      continue;
+
+    if (ranges.blocks() + scratch_blocks_found > blocks_needed) {
+      // trim down ranges
+      vector<Extent> new_ranges = ranges.GetExtentsForBlockCount(
+          blocks_needed - scratch_blocks_found);
+      ranges = ExtentRanges();
+      ranges.AddExtents(new_ranges);
+    }
+    scratch_ranges.AddRanges(ranges);
+    block_suppliers.push_back(make_pair(test_node, ranges));
+    scratch_blocks_found += ranges.blocks();
+    if (scratch_ranges.blocks() >= blocks_needed)
+      break;
+  }
+  if (scratch_ranges.blocks() < blocks_needed) {
+    LOG(INFO) << "Unable to find sufficient scratch";
+    TEST_AND_RETURN_FALSE(ConvertCutsToFull(graph,
+                                            new_part,
+                                            data_fd,
+                                            data_file_size,
+                                            op_indexes,
+                                            reverse_op_indexes,
+                                            cuts));
+    return true;
+  }
+  // Use the scratch we found
+  TEST_AND_RETURN_FALSE(scratch_ranges.blocks() == scratch_blocks_found);
+
+  // Make all the suppliers depend on this node
+  for (const auto& index_range_pair : block_suppliers) {
+    graph_utils::AddReadBeforeDepExtents(
+        &(*graph)[index_range_pair.first],
+        old_dst,
+        index_range_pair.second.GetExtentsForBlockCount(
+            index_range_pair.second.blocks()));
+  }
+
+  // Replace temp blocks in each cut
+  for (vector<CutEdgeVertexes>::size_type i = 0; i < cuts.size(); ++i) {
+    const CutEdgeVertexes& cut = cuts[i];
+    vector<Extent> real_extents =
+        scratch_ranges.GetExtentsForBlockCount(cuts_blocks_needed[i]);
+    scratch_ranges.SubtractExtents(real_extents);
+
+    // Fix the old dest node w/ the real blocks
+    InplaceGenerator::SubstituteBlocks(&(*graph)[old_dst],
+                                         cut.tmp_extents,
+                                         real_extents);
+
+    // Fix the new node w/ the real blocks. Since the new node is just a
+    // copy operation, we can replace all the dest extents w/ the real
+    // blocks.
+    DeltaArchiveManifest_InstallOperation *op = &(*graph)[cut.new_vertex].op;
+    op->clear_dst_extents();
+    StoreExtents(real_extents, op->mutable_dst_extents());
+  }
+  return true;
+}
+
+}  // namespace
+
+bool InplaceGenerator::AssignTempBlocks(
+    Graph* graph,
+    const string& new_part,
+    int data_fd,
+    off_t* data_file_size,
+    vector<Vertex::Index>* op_indexes,
+    vector<vector<Vertex::Index>::size_type>* reverse_op_indexes,
+    const vector<CutEdgeVertexes>& cuts) {
+  CHECK(!cuts.empty());
+
+  // group of cuts w/ the same old_dst:
+  vector<CutEdgeVertexes> cuts_group;
+
+  for (vector<CutEdgeVertexes>::size_type i = cuts.size() - 1, e = 0;
+       true ; --i) {
+    LOG(INFO) << "Fixing temp blocks in cut " << i
+              << ": old dst: " << cuts[i].old_dst << " new vertex: "
+              << cuts[i].new_vertex << " path: "
+              << (*graph)[cuts[i].old_dst].file_name;
+
+    if (cuts_group.empty() || (cuts_group[0].old_dst == cuts[i].old_dst)) {
+      cuts_group.push_back(cuts[i]);
+    } else {
+      CHECK(!cuts_group.empty());
+      TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph,
+                                                        new_part,
+                                                        data_fd,
+                                                        data_file_size,
+                                                        op_indexes,
+                                                        reverse_op_indexes,
+                                                        cuts_group));
+      cuts_group.clear();
+      cuts_group.push_back(cuts[i]);
+    }
+
+    if (i == e) {
+      // break out of for() loop
+      break;
+    }
+  }
+  CHECK(!cuts_group.empty());
+  TEST_AND_RETURN_FALSE(AssignBlockForAdjoiningCuts(graph,
+                                                    new_part,
+                                                    data_fd,
+                                                    data_file_size,
+                                                    op_indexes,
+                                                    reverse_op_indexes,
+                                                    cuts_group));
+  return true;
+}
+
+bool InplaceGenerator::NoTempBlocksRemain(const Graph& graph) {
+  size_t idx = 0;
+  for (Graph::const_iterator it = graph.begin(), e = graph.end(); it != e;
+       ++it, ++idx) {
+    if (!it->valid)
+      continue;
+    const DeltaArchiveManifest_InstallOperation& op = it->op;
+    if (TempBlocksExistInExtents(op.dst_extents()) ||
+        TempBlocksExistInExtents(op.src_extents())) {
+      LOG(INFO) << "bad extents in node " << idx;
+      LOG(INFO) << "so yeah";
+      return false;
+    }
+
+    // Check out-edges:
+    for (const auto& edge_prop_pair : it->out_edges) {
+      if (TempBlocksExistInExtents(edge_prop_pair.second.extents) ||
+          TempBlocksExistInExtents(edge_prop_pair.second.write_extents)) {
+        LOG(INFO) << "bad out edge in node " << idx;
+        LOG(INFO) << "so yeah";
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool InplaceGenerator::ConvertCutToFullOp(Graph* graph,
+                                          const CutEdgeVertexes& cut,
+                                          const string& new_part,
+                                          int data_fd,
+                                          off_t* data_file_size) {
+  // Drop all incoming edges, keep all outgoing edges
+
+  // Keep all outgoing edges
+  if ((*graph)[cut.old_dst].op.type() !=
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ &&
+      (*graph)[cut.old_dst].op.type() !=
+      DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
+    Vertex::EdgeMap out_edges = (*graph)[cut.old_dst].out_edges;
+    graph_utils::DropWriteBeforeDeps(&out_edges);
+
+    // Replace the operation with a REPLACE or REPLACE_BZ to generate the same
+    // |new_extents| list of blocks and update the graph.
+    vector<AnnotatedOperation> new_aop;
+    vector<Extent> new_extents;
+    ExtentsToVector((*graph)[cut.old_dst].op.dst_extents(),
+                    &new_extents);
+    TEST_AND_RETURN_FALSE(diff_utils::DeltaReadFile(
+        &new_aop,
+        "",  // old_part
+        new_part,
+        vector<Extent>(),  // old_extents
+        new_extents,
+        (*graph)[cut.old_dst].file_name,
+        -1,  // chunk_blocks, forces to have a single operation.
+        data_fd,
+        data_file_size,
+        false));  // src_ops_allowed
+    TEST_AND_RETURN_FALSE(new_aop.size() == 1);
+    TEST_AND_RETURN_FALSE(AddInstallOpToGraph(
+      graph, cut.old_dst, nullptr, new_aop.front().op, new_aop.front().name));
+
+    (*graph)[cut.old_dst].out_edges = out_edges;
+
+    // Right now we don't have doubly-linked edges, so we have to scan
+    // the whole graph.
+    graph_utils::DropIncomingEdgesTo(graph, cut.old_dst);
+  }
+
+  // Delete temp node
+  (*graph)[cut.old_src].out_edges.erase(cut.new_vertex);
+  CHECK((*graph)[cut.old_dst].out_edges.find(cut.new_vertex) ==
+        (*graph)[cut.old_dst].out_edges.end());
+  (*graph)[cut.new_vertex].valid = false;
+  LOG(INFO) << "marked node invalid: " << cut.new_vertex;
+  return true;
+}
+
+bool InplaceGenerator::ConvertGraphToDag(Graph* graph,
+                                         const string& new_part,
+                                         int fd,
+                                         off_t* data_file_size,
+                                         vector<Vertex::Index>* final_order,
+                                         Vertex::Index scratch_vertex) {
+  CycleBreaker cycle_breaker;
+  LOG(INFO) << "Finding cycles...";
+  set<Edge> cut_edges;
+  cycle_breaker.BreakCycles(*graph, &cut_edges);
+  LOG(INFO) << "done finding cycles";
+  CheckGraph(*graph);
+
+  // Calculate number of scratch blocks needed
+
+  LOG(INFO) << "Cutting cycles...";
+  vector<CutEdgeVertexes> cuts;
+  TEST_AND_RETURN_FALSE(CutEdges(graph, cut_edges, &cuts));
+  LOG(INFO) << "done cutting cycles";
+  LOG(INFO) << "There are " << cuts.size() << " cuts.";
+  CheckGraph(*graph);
+
+  LOG(INFO) << "Creating initial topological order...";
+  TopologicalSort(*graph, final_order);
+  LOG(INFO) << "done with initial topo order";
+  CheckGraph(*graph);
+
+  LOG(INFO) << "Moving full ops to the back";
+  MoveFullOpsToBack(graph, final_order);
+  LOG(INFO) << "done moving full ops to back";
+
+  vector<vector<Vertex::Index>::size_type> inverse_final_order;
+  GenerateReverseTopoOrderMap(*final_order, &inverse_final_order);
+
+  SortCutsByTopoOrder(*final_order, &cuts);
+
+  if (!cuts.empty())
+    TEST_AND_RETURN_FALSE(AssignTempBlocks(graph,
+                                           new_part,
+                                           fd,
+                                           data_file_size,
+                                           final_order,
+                                           &inverse_final_order,
+                                           cuts));
+  LOG(INFO) << "Making sure all temp blocks have been allocated";
+
+  // Remove the scratch node, if any
+  if (scratch_vertex != Vertex::kInvalidIndex) {
+    final_order->erase(final_order->begin() +
+                       inverse_final_order[scratch_vertex]);
+    (*graph)[scratch_vertex].valid = false;
+    GenerateReverseTopoOrderMap(*final_order, &inverse_final_order);
+  }
+
+  graph_utils::DumpGraph(*graph);
+  CHECK(NoTempBlocksRemain(*graph));
+  LOG(INFO) << "done making sure all temp blocks are allocated";
+  return true;
+}
+
+void InplaceGenerator::CreateScratchNode(uint64_t start_block,
+                                         uint64_t num_blocks,
+                                         Vertex* vertex) {
+  vertex->file_name = "<scratch>";
+  vertex->op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+  vertex->op.set_data_offset(0);
+  vertex->op.set_data_length(0);
+  Extent* extent = vertex->op.add_dst_extents();
+  extent->set_start_block(start_block);
+  extent->set_num_blocks(num_blocks);
+}
+
+bool InplaceGenerator::AddInstallOpToBlocksVector(
+    const DeltaArchiveManifest_InstallOperation& operation,
+    const Graph& graph,
+    Vertex::Index vertex,
+    vector<Block>* blocks) {
+  // See if this is already present.
+  TEST_AND_RETURN_FALSE(operation.dst_extents_size() > 0);
+
+  enum BlockField { READER = 0, WRITER, BLOCK_FIELD_COUNT };
+  for (int field = READER; field < BLOCK_FIELD_COUNT; field++) {
+    const int extents_size =
+        (field == READER) ? operation.src_extents_size() :
+        operation.dst_extents_size();
+    const char* past_participle = (field == READER) ? "read" : "written";
+    const google::protobuf::RepeatedPtrField<Extent>& extents =
+        (field == READER) ? operation.src_extents() : operation.dst_extents();
+    Vertex::Index Block::*access_type = (field == READER) ?
+        &Block::reader : &Block::writer;
+
+    for (int i = 0; i < extents_size; i++) {
+      const Extent& extent = extents.Get(i);
+      for (uint64_t block = extent.start_block();
+           block < (extent.start_block() + extent.num_blocks()); block++) {
+        if ((*blocks)[block].*access_type != Vertex::kInvalidIndex) {
+          LOG(FATAL) << "Block " << block << " is already "
+                     << past_participle << " by "
+                     << (*blocks)[block].*access_type << "("
+                     << graph[(*blocks)[block].*access_type].file_name
+                     << ") and also " << vertex << "("
+                     << graph[vertex].file_name << ")";
+        }
+        (*blocks)[block].*access_type = vertex;
+      }
+    }
+  }
+  return true;
+}
+
+bool InplaceGenerator::AddInstallOpToGraph(
+    Graph* graph,
+    Vertex::Index existing_vertex,
+    vector<Block>* blocks,
+    const DeltaArchiveManifest_InstallOperation operation,
+    const string& op_name) {
+  Vertex::Index vertex = existing_vertex;
+  if (vertex == Vertex::kInvalidIndex) {
+    graph->emplace_back();
+    vertex = graph->size() - 1;
+  }
+  (*graph)[vertex].op = operation;
+  CHECK((*graph)[vertex].op.has_type());
+  (*graph)[vertex].file_name = op_name;
+
+  if (blocks)
+    TEST_AND_RETURN_FALSE(InplaceGenerator::AddInstallOpToBlocksVector(
+        (*graph)[vertex].op,
+        *graph,
+        vertex,
+        blocks));
+  return true;
+}
+
+void InplaceGenerator::ApplyMap(vector<uint64_t>* collection,
+                                const map<uint64_t, uint64_t>& the_map) {
+  for (uint64_t& elem : *collection) {
+    const auto& map_it = the_map.find(elem);
+    if (map_it != the_map.end())
+      elem = map_it->second;
+  }
+}
+
+bool InplaceGenerator::GenerateOperations(
+    const PayloadGenerationConfig& config,
+    int data_file_fd,
+    off_t* data_file_size,
+    vector<AnnotatedOperation>* rootfs_ops,
+    vector<AnnotatedOperation>* kernel_ops) {
+  off_t chunk_blocks = (config.chunk_size == -1 ? -1 :
+                        config.chunk_size / config.block_size);
+
+  // Temporary list of operations used to construct the dependency graph.
+  vector<AnnotatedOperation> aops;
+  TEST_AND_RETURN_FALSE(
+      diff_utils::DeltaReadPartition(&aops,
+                                     config.source.rootfs,
+                                     config.target.rootfs,
+                                     chunk_blocks,
+                                     data_file_fd,
+                                     data_file_size,
+                                     true,  // skip_block_0
+                                     false));  // src_ops_allowed
+  // Convert the rootfs operations to the graph.
+  Graph graph;
+  CheckGraph(graph);
+  vector<Block> blocks(config.target.rootfs.size / config.block_size);
+  for (const auto& aop : aops) {
+    AddInstallOpToGraph(
+        &graph, Vertex::kInvalidIndex, &blocks, aop.op, aop.name);
+  }
+  LOG(INFO) << "done reading normal files";
+  CheckGraph(graph);
+
+  // 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();
+    CreateScratchNode(
+        blocks.size(),
+        (config.rootfs_partition_size / kBlockSize) - blocks.size(),
+        &graph.back());
+  }
+
+  // Read kernel partition
+  LOG(INFO) << "Delta compressing kernel partition...";
+  // It is safe to not skip the block 0 since we will not be using the cycle
+  // breaking algorithm on this list of operations as we expect no cycles here.
+  TEST_AND_RETURN_FALSE(
+      diff_utils::DeltaReadPartition(kernel_ops,
+                                     config.source.kernel,
+                                     config.target.kernel,
+                                     chunk_blocks,
+                                     data_file_fd,
+                                     data_file_size,
+                                     false,  // skip_block_0
+                                     false));  // src_ops_allowed
+  LOG(INFO) << "done reading kernel";
+  CheckGraph(graph);
+
+  LOG(INFO) << "Creating edges...";
+  CreateEdges(&graph, blocks);
+  LOG(INFO) << "Done creating edges";
+  CheckGraph(graph);
+
+  vector<Vertex::Index> final_order;
+  TEST_AND_RETURN_FALSE(ConvertGraphToDag(
+      &graph,
+      config.target.rootfs.path,
+      data_file_fd,
+      data_file_size,
+      &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().name = vertex.file_name;
+  }
+
+  // Re-add the operation for the block 0.
+  TEST_AND_RETURN_FALSE(diff_utils::DeltaReadFile(
+      rootfs_ops,
+      config.source.rootfs.path,
+      config.target.rootfs.path,
+      vector<Extent>{ExtentForRange(0, 1)},
+      vector<Extent>{ExtentForRange(0, 1)},
+      "<block-0>",  // operation name
+      -1,  // chunk_blocks
+      data_file_fd,
+      data_file_size,
+      false));  // src_ops_allowed
+
+  return true;
+}
+
+};  // namespace chromeos_update_engine
diff --git a/payload_generator/inplace_generator.h b/payload_generator/inplace_generator.h
new file mode 100644
index 0000000..158e364
--- /dev/null
+++ b/payload_generator/inplace_generator.h
@@ -0,0 +1,221 @@
+// 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_INPLACE_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#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
+// version is 1.
+
+namespace chromeos_update_engine {
+
+// This struct stores all relevant info for an edge that is cut between
+// nodes old_src -> old_dst by creating new vertex new_vertex. The new
+// relationship is:
+// old_src -(read before)-> new_vertex <-(write before)- old_dst
+// new_vertex is a MOVE operation that moves some existing blocks into
+// temp space. The temp extents are, by necessity, stored in new_vertex
+// (as dst extents) and old_dst (as src extents), but they are also broken
+// out into tmp_extents, as the nodes themselves may contain many more
+// extents.
+struct CutEdgeVertexes {
+  Vertex::Index new_vertex;
+  Vertex::Index old_src;
+  Vertex::Index old_dst;
+  std::vector<Extent> tmp_extents;
+};
+
+class InplaceGenerator : public OperationsGenerator {
+ public:
+  // Represents a disk block on the install partition.
+  struct Block {
+    // During install, each block on the install partition will be written
+    // and some may be read (in all likelihood, many will be read).
+    // The reading and writing will be performed by InstallOperations,
+    // each of which has a corresponding vertex in a graph.
+    // A Block object tells which vertex will read or write this block
+    // at install time.
+    // Generally, there will be a vector of Block objects whose length
+    // is the number of blocks on the install partition.
+    Block() : reader(Vertex::kInvalidIndex), writer(Vertex::kInvalidIndex) {}
+    Vertex::Index reader;
+    Vertex::Index writer;
+  };
+
+  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.
+  // Blocks will be substituted in the order listed in the vectors.
+  // E.g. if 'op' reads blocks 1, 2, 3, 4, 5, 6, 7, 8, remove_extents
+  // contains blocks 6, 2, 3, 5, and replace blocks contains
+  // 12, 13, 14, 15, then op will be changed to read from:
+  // 1, 13, 14, 4, 15, 12, 7, 8
+  static void SubstituteBlocks(Vertex* vertex,
+                               const std::vector<Extent>& remove_extents,
+                               const std::vector<Extent>& replace_extents);
+
+  // Cuts 'edges' from 'graph' according to the AU algorithm. This means
+  // for each edge A->B, remove the dependency that B occur before A.
+  // Do this by creating a new operation X that copies from the blocks
+  // specified by the edge's properties to temp space T. Modify B to read
+  // from T rather than the blocks in the edge. Modify A to depend on X,
+  // but not on B. Free space is found by looking in 'blocks'.
+  // Returns true on success.
+  static bool CutEdges(Graph* graph,
+                       const std::set<Edge>& edges,
+                       std::vector<CutEdgeVertexes>* out_cuts);
+
+  // Creates all the edges for the graph. Writers of a block point to
+  // readers of the same block. This is because for an edge A->B, B
+  // must complete before A executes.
+  static void CreateEdges(Graph* graph,
+                          const std::vector<Block>& blocks);
+
+  // Takes |op_indexes|, which is effectively a mapping from order in
+  // which the op is performed -> graph vertex index, and produces the
+  // reverse: a mapping from graph vertex index -> op_indexes index.
+  static void GenerateReverseTopoOrderMap(
+      const std::vector<Vertex::Index>& op_indexes,
+      std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes);
+
+  // Sorts the vector |cuts| by its |cuts[].old_dest| member. Order is
+  // determined by the order of elements in op_indexes.
+  static void SortCutsByTopoOrder(
+      const std::vector<Vertex::Index>& op_indexes,
+      std::vector<CutEdgeVertexes>* cuts);
+
+  // Given a topologically sorted graph |op_indexes| and |graph|, alters
+  // |op_indexes| to move all the full operations to the end of the vector.
+  // Full operations should not be depended on, so this is safe.
+  static void MoveFullOpsToBack(Graph* graph,
+                                std::vector<Vertex::Index>* op_indexes);
+
+  // Returns true iff there are no extents in the graph that refer to temp
+  // blocks. Temp blocks are in the range [kTempBlockStart, kSparseHole).
+  static bool NoTempBlocksRemain(const Graph& graph);
+
+  // Takes a |graph|, which has edges that must be cut, as listed in
+  // |cuts|.  Cuts the edges. Maintains a list in which the operations
+  // will be performed (in |op_indexes|) and the reverse (in
+  // |reverse_op_indexes|).  Cutting edges requires scratch space, and
+  // if insufficient scratch is found, the file is reread and will be
+  // send down (either as REPLACE or REPLACE_BZ).  Returns true on
+  // success.
+  static bool AssignTempBlocks(
+      Graph* graph,
+      const std::string& new_part,
+      int data_fd,
+      off_t* data_file_size,
+      std::vector<Vertex::Index>* op_indexes,
+      std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes,
+      const std::vector<CutEdgeVertexes>& cuts);
+
+  // Handles allocation of temp blocks to a cut edge by converting the
+  // dest node to a full op. This removes the need for temp blocks, but
+  // comes at the cost of a worse compression ratio.
+  // For example, say we have A->B->A. It would first be cut to form:
+  // A->B->N<-A, where N copies blocks to temp space. If there are no
+  // temp blocks, this function can be called to convert it to the form:
+  // A->B. Now, A is a full operation.
+  static bool ConvertCutToFullOp(Graph* graph,
+                                 const CutEdgeVertexes& cut,
+                                 const std::string& new_part,
+                                 int data_fd,
+                                 off_t* data_file_size);
+
+  // Takes a graph, which is not a DAG, which represents the files just
+  // read from disk, and converts it into a DAG by breaking all cycles
+  // and finding temp space to resolve broken edges.
+  // The final order of the nodes is given in |final_order|
+  // Some files may need to be reread from disk, thus |fd| and
+  // |data_file_size| are be passed.
+  // If |scratch_vertex| is not kInvalidIndex, removes it from
+  // |final_order| before returning.
+  // Returns true on success.
+  static bool ConvertGraphToDag(Graph* graph,
+                                const std::string& new_part,
+                                int fd,
+                                off_t* data_file_size,
+                                std::vector<Vertex::Index>* final_order,
+                                Vertex::Index scratch_vertex);
+
+  // Creates a dummy REPLACE_BZ node in the given |vertex|. This can be used
+  // to provide scratch space. The node writes |num_blocks| blocks starting at
+  // |start_block|The node should be marked invalid before writing all nodes to
+  // the output file.
+  static void CreateScratchNode(uint64_t start_block,
+                                uint64_t num_blocks,
+                                Vertex* vertex);
+
+  // 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.
+  static bool AddInstallOpToBlocksVector(
+      const DeltaArchiveManifest_InstallOperation& operation,
+      const Graph& graph,
+      Vertex::Index vertex,
+      std::vector<Block>* blocks);
+
+  // Add a vertex (if |existing_vertex| is kInvalidVertex) or update an
+  // |existing_vertex| with the passed |operation|.
+  // This method will also register the vertex as the reader or writer of the
+  // blocks involved in the operation updating the |blocks| vector. The
+  // |op_name| associated with the Vertex is used for logging purposes.
+  static bool AddInstallOpToGraph(
+      Graph* graph,
+      Vertex::Index existing_vertex,
+      std::vector<Block>* blocks,
+      const DeltaArchiveManifest_InstallOperation operation,
+      const std::string& op_name);
+
+  // Apply the transformation stored in |the_map| to the |collection| vector
+  // replacing the map keys found in |collection| with its associated value in
+  // |the_map|.
+  static void ApplyMap(std::vector<uint64_t>* collection,
+                       const std::map<uint64_t, uint64_t>& the_map);
+
+  // Generate the update payload operations for the kernel and rootfs using
+  // only operations that read from the target and/or write to the target,
+  // hence, applying the payload "in-place" in the target partition. This method
+  // assumes that the contents of the source image are pre-copied to the target
+  // partition, up to the size of the source image. Use this method to generate
+  // a delta update with the minor version kInPlaceMinorPayloadVersion.
+  // 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 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|.
+  bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      int data_file_fd,
+      off_t* data_file_size,
+      std::vector<AnnotatedOperation>* rootfs_ops,
+      std::vector<AnnotatedOperation>* kernel_ops) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InplaceGenerator);
+};
+
+};  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
diff --git a/payload_generator/inplace_generator_unittest.cc b/payload_generator/inplace_generator_unittest.cc
new file mode 100644
index 0000000..5de0df9
--- /dev/null
+++ b/payload_generator/inplace_generator_unittest.cc
@@ -0,0 +1,518 @@
+// 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/inplace_generator.h"
+
+#include <map>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/cycle_breaker.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using std::map;
+using std::set;
+using std::string;
+using std::stringstream;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+using Block = InplaceGenerator::Block;
+
+namespace {
+
+#define OP_BSDIFF DeltaArchiveManifest_InstallOperation_Type_BSDIFF
+#define OP_MOVE DeltaArchiveManifest_InstallOperation_Type_MOVE
+#define OP_REPLACE DeltaArchiveManifest_InstallOperation_Type_REPLACE
+#define OP_REPLACE_BZ DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ
+
+void GenVertex(Vertex* out,
+               const vector<Extent>& src_extents,
+               const vector<Extent>& dst_extents,
+               const string& path,
+               DeltaArchiveManifest_InstallOperation_Type type) {
+  out->op.set_type(type);
+  out->file_name = path;
+  StoreExtents(src_extents, out->op.mutable_src_extents());
+  StoreExtents(dst_extents, out->op.mutable_dst_extents());
+}
+
+vector<Extent> VectOfExt(uint64_t start_block, uint64_t num_blocks) {
+  return vector<Extent>(1, ExtentForRange(start_block, num_blocks));
+}
+
+EdgeProperties EdgeWithReadDep(const vector<Extent>& extents) {
+  EdgeProperties ret;
+  ret.extents = extents;
+  return ret;
+}
+
+EdgeProperties EdgeWithWriteDep(const vector<Extent>& extents) {
+  EdgeProperties ret;
+  ret.write_extents = extents;
+  return ret;
+}
+
+template<typename T>
+void DumpVect(const vector<T>& vect) {
+  stringstream ss(stringstream::out);
+  for (typename vector<T>::const_iterator it = vect.begin(), e = vect.end();
+       it != e; ++it) {
+    ss << *it << ", ";
+  }
+  LOG(INFO) << "{" << ss.str() << "}";
+}
+
+void AppendExtent(vector<Extent>* vect, uint64_t start, uint64_t length) {
+  vect->resize(vect->size() + 1);
+  vect->back().set_start_block(start);
+  vect->back().set_num_blocks(length);
+}
+
+void OpAppendExtent(DeltaArchiveManifest_InstallOperation* op,
+                    uint64_t start,
+                    uint64_t length) {
+  Extent* extent = op->add_src_extents();
+  extent->set_start_block(start);
+  extent->set_num_blocks(length);
+}
+
+}  // namespace
+
+class InplaceGeneratorTest : public ::testing::Test {
+};
+
+TEST_F(InplaceGeneratorTest, BlockDefaultValues) {
+  // Tests that a Block is initialized with the default values as a
+  // Vertex::kInvalidIndex. This is required by the delta generators.
+  Block block;
+  EXPECT_EQ(Vertex::kInvalidIndex, block.reader);
+  EXPECT_EQ(Vertex::kInvalidIndex, block.writer);
+}
+
+TEST_F(InplaceGeneratorTest, SubstituteBlocksTest) {
+  vector<Extent> remove_blocks;
+  AppendExtent(&remove_blocks, 3, 3);
+  AppendExtent(&remove_blocks, 7, 1);
+  vector<Extent> replace_blocks;
+  AppendExtent(&replace_blocks, 10, 2);
+  AppendExtent(&replace_blocks, 13, 2);
+  Vertex vertex;
+  DeltaArchiveManifest_InstallOperation& op = vertex.op;
+  OpAppendExtent(&op, 4, 3);
+  OpAppendExtent(&op, kSparseHole, 4);  // Sparse hole in file
+  OpAppendExtent(&op, 3, 1);
+  OpAppendExtent(&op, 7, 3);
+
+  InplaceGenerator::SubstituteBlocks(&vertex, remove_blocks, replace_blocks);
+
+  EXPECT_EQ(7, op.src_extents_size());
+  EXPECT_EQ(11, op.src_extents(0).start_block());
+  EXPECT_EQ(1, op.src_extents(0).num_blocks());
+  EXPECT_EQ(13, op.src_extents(1).start_block());
+  EXPECT_EQ(1, op.src_extents(1).num_blocks());
+  EXPECT_EQ(6, op.src_extents(2).start_block());
+  EXPECT_EQ(1, op.src_extents(2).num_blocks());
+  EXPECT_EQ(kSparseHole, op.src_extents(3).start_block());
+  EXPECT_EQ(4, op.src_extents(3).num_blocks());
+  EXPECT_EQ(10, op.src_extents(4).start_block());
+  EXPECT_EQ(1, op.src_extents(4).num_blocks());
+  EXPECT_EQ(14, op.src_extents(5).start_block());
+  EXPECT_EQ(1, op.src_extents(5).num_blocks());
+  EXPECT_EQ(8, op.src_extents(6).start_block());
+  EXPECT_EQ(2, op.src_extents(6).num_blocks());
+}
+
+TEST_F(InplaceGeneratorTest, CutEdgesTest) {
+  Graph graph;
+  vector<Block> blocks(9);
+
+  // Create nodes in graph
+  {
+    graph.resize(graph.size() + 1);
+    graph.back().op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+    // Reads from blocks 3, 5, 7
+    vector<Extent> extents;
+    AppendBlockToExtents(&extents, 3);
+    AppendBlockToExtents(&extents, 5);
+    AppendBlockToExtents(&extents, 7);
+    StoreExtents(extents,
+                                     graph.back().op.mutable_src_extents());
+    blocks[3].reader = graph.size() - 1;
+    blocks[5].reader = graph.size() - 1;
+    blocks[7].reader = graph.size() - 1;
+
+    // Writes to blocks 1, 2, 4
+    extents.clear();
+    AppendBlockToExtents(&extents, 1);
+    AppendBlockToExtents(&extents, 2);
+    AppendBlockToExtents(&extents, 4);
+    StoreExtents(extents,
+                                     graph.back().op.mutable_dst_extents());
+    blocks[1].writer = graph.size() - 1;
+    blocks[2].writer = graph.size() - 1;
+    blocks[4].writer = graph.size() - 1;
+  }
+  {
+    graph.resize(graph.size() + 1);
+    graph.back().op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+    // Reads from blocks 1, 2, 4
+    vector<Extent> extents;
+    AppendBlockToExtents(&extents, 1);
+    AppendBlockToExtents(&extents, 2);
+    AppendBlockToExtents(&extents, 4);
+    StoreExtents(extents,
+                                     graph.back().op.mutable_src_extents());
+    blocks[1].reader = graph.size() - 1;
+    blocks[2].reader = graph.size() - 1;
+    blocks[4].reader = graph.size() - 1;
+
+    // Writes to blocks 3, 5, 6
+    extents.clear();
+    AppendBlockToExtents(&extents, 3);
+    AppendBlockToExtents(&extents, 5);
+    AppendBlockToExtents(&extents, 6);
+    StoreExtents(extents,
+                                     graph.back().op.mutable_dst_extents());
+    blocks[3].writer = graph.size() - 1;
+    blocks[5].writer = graph.size() - 1;
+    blocks[6].writer = graph.size() - 1;
+  }
+
+  // Create edges
+  InplaceGenerator::CreateEdges(&graph, blocks);
+
+  // Find cycles
+  CycleBreaker cycle_breaker;
+  set<Edge> cut_edges;
+  cycle_breaker.BreakCycles(graph, &cut_edges);
+
+  EXPECT_EQ(1, cut_edges.size());
+  EXPECT_TRUE(cut_edges.end() != cut_edges.find(
+      std::pair<Vertex::Index, Vertex::Index>(1, 0)));
+
+  vector<CutEdgeVertexes> cuts;
+  EXPECT_TRUE(InplaceGenerator::CutEdges(&graph, cut_edges, &cuts));
+
+  EXPECT_EQ(3, graph.size());
+
+  // Check new node in graph:
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_MOVE,
+            graph.back().op.type());
+  EXPECT_EQ(2, graph.back().op.src_extents_size());
+  EXPECT_EQ(1, graph.back().op.dst_extents_size());
+  EXPECT_EQ(kTempBlockStart, graph.back().op.dst_extents(0).start_block());
+  EXPECT_EQ(2, graph.back().op.dst_extents(0).num_blocks());
+  EXPECT_TRUE(graph.back().out_edges.empty());
+
+  // Check that old node reads from new blocks
+  EXPECT_EQ(2, graph[0].op.src_extents_size());
+  EXPECT_EQ(kTempBlockStart, graph[0].op.src_extents(0).start_block());
+  EXPECT_EQ(2, graph[0].op.src_extents(0).num_blocks());
+  EXPECT_EQ(7, graph[0].op.src_extents(1).start_block());
+  EXPECT_EQ(1, graph[0].op.src_extents(1).num_blocks());
+
+  // And that the old dst extents haven't changed
+  EXPECT_EQ(2, graph[0].op.dst_extents_size());
+  EXPECT_EQ(1, graph[0].op.dst_extents(0).start_block());
+  EXPECT_EQ(2, graph[0].op.dst_extents(0).num_blocks());
+  EXPECT_EQ(4, graph[0].op.dst_extents(1).start_block());
+  EXPECT_EQ(1, graph[0].op.dst_extents(1).num_blocks());
+
+  // Ensure it only depends on the next node and the new temp node
+  EXPECT_EQ(2, graph[0].out_edges.size());
+  EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(1));
+  EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(graph.size() -
+                                                                  1));
+
+  // Check second node has unchanged extents
+  EXPECT_EQ(2, graph[1].op.src_extents_size());
+  EXPECT_EQ(1, graph[1].op.src_extents(0).start_block());
+  EXPECT_EQ(2, graph[1].op.src_extents(0).num_blocks());
+  EXPECT_EQ(4, graph[1].op.src_extents(1).start_block());
+  EXPECT_EQ(1, graph[1].op.src_extents(1).num_blocks());
+
+  EXPECT_EQ(2, graph[1].op.dst_extents_size());
+  EXPECT_EQ(3, graph[1].op.dst_extents(0).start_block());
+  EXPECT_EQ(1, graph[1].op.dst_extents(0).num_blocks());
+  EXPECT_EQ(5, graph[1].op.dst_extents(1).start_block());
+  EXPECT_EQ(2, graph[1].op.dst_extents(1).num_blocks());
+
+  // Ensure it only depends on the next node
+  EXPECT_EQ(1, graph[1].out_edges.size());
+  EXPECT_TRUE(graph[1].out_edges.end() != graph[1].out_edges.find(2));
+}
+
+TEST_F(InplaceGeneratorTest, AssignTempBlocksReuseTest) {
+  Graph graph(9);
+
+  const vector<Extent> empt;
+  uint64_t tmp = kTempBlockStart;
+  const string kFilename = "/foo";
+
+  vector<CutEdgeVertexes> cuts;
+  cuts.resize(3);
+
+  // Simple broken loop:
+  GenVertex(&graph[0], VectOfExt(0, 1), VectOfExt(1, 1), "", OP_MOVE);
+  GenVertex(&graph[1], VectOfExt(tmp, 1), VectOfExt(0, 1), "", OP_MOVE);
+  GenVertex(&graph[2], VectOfExt(1, 1), VectOfExt(tmp, 1), "", OP_MOVE);
+  // Corresponding edges:
+  graph[0].out_edges[2] = EdgeWithReadDep(VectOfExt(1, 1));
+  graph[1].out_edges[2] = EdgeWithWriteDep(VectOfExt(tmp, 1));
+  graph[1].out_edges[0] = EdgeWithReadDep(VectOfExt(0, 1));
+  // Store the cut:
+  cuts[0].old_dst = 1;
+  cuts[0].old_src = 0;
+  cuts[0].new_vertex = 2;
+  cuts[0].tmp_extents = VectOfExt(tmp, 1);
+  tmp++;
+
+  // Slightly more complex pair of loops:
+  GenVertex(&graph[3], VectOfExt(4, 2), VectOfExt(2, 2), "", OP_MOVE);
+  GenVertex(&graph[4], VectOfExt(6, 1), VectOfExt(7, 1), "", OP_MOVE);
+  GenVertex(&graph[5], VectOfExt(tmp, 3), VectOfExt(4, 3), kFilename, OP_MOVE);
+  GenVertex(&graph[6], VectOfExt(2, 2), VectOfExt(tmp, 2), "", OP_MOVE);
+  GenVertex(&graph[7], VectOfExt(7, 1), VectOfExt(tmp + 2, 1), "", OP_MOVE);
+  // Corresponding edges:
+  graph[3].out_edges[6] = EdgeWithReadDep(VectOfExt(2, 2));
+  graph[4].out_edges[7] = EdgeWithReadDep(VectOfExt(7, 1));
+  graph[5].out_edges[6] = EdgeWithWriteDep(VectOfExt(tmp, 2));
+  graph[5].out_edges[7] = EdgeWithWriteDep(VectOfExt(tmp + 2, 1));
+  graph[5].out_edges[3] = EdgeWithReadDep(VectOfExt(4, 2));
+  graph[5].out_edges[4] = EdgeWithReadDep(VectOfExt(6, 1));
+  // Store the cuts:
+  cuts[1].old_dst = 5;
+  cuts[1].old_src = 3;
+  cuts[1].new_vertex = 6;
+  cuts[1].tmp_extents = VectOfExt(tmp, 2);
+  cuts[2].old_dst = 5;
+  cuts[2].old_src = 4;
+  cuts[2].new_vertex = 7;
+  cuts[2].tmp_extents = VectOfExt(tmp + 2, 1);
+
+  // Supplier of temp block:
+  GenVertex(&graph[8], empt, VectOfExt(8, 1), "", OP_REPLACE);
+
+  // Specify the final order:
+  vector<Vertex::Index> op_indexes;
+  op_indexes.push_back(2);
+  op_indexes.push_back(0);
+  op_indexes.push_back(1);
+  op_indexes.push_back(6);
+  op_indexes.push_back(3);
+  op_indexes.push_back(7);
+  op_indexes.push_back(4);
+  op_indexes.push_back(5);
+  op_indexes.push_back(8);
+
+  vector<vector<Vertex::Index>::size_type> reverse_op_indexes;
+  InplaceGenerator::GenerateReverseTopoOrderMap(op_indexes,
+                                                &reverse_op_indexes);
+
+  int fd;
+  EXPECT_TRUE(utils::MakeTempFile("AssignTempBlocksReuseTest.XXXXXX",
+                                  nullptr,
+                                  &fd));
+  ScopedFdCloser fd_closer(&fd);
+  off_t data_file_size = 0;
+
+  EXPECT_TRUE(InplaceGenerator::AssignTempBlocks(&graph,
+                                                 "/dev/zero",
+                                                 fd,
+                                                 &data_file_size,
+                                                 &op_indexes,
+                                                 &reverse_op_indexes,
+                                                 cuts));
+  EXPECT_FALSE(graph[6].valid);
+  EXPECT_FALSE(graph[7].valid);
+  EXPECT_EQ(1, graph[1].op.src_extents_size());
+  EXPECT_EQ(2, graph[1].op.src_extents(0).start_block());
+  EXPECT_EQ(1, graph[1].op.src_extents(0).num_blocks());
+  EXPECT_EQ(OP_REPLACE_BZ, graph[5].op.type());
+}
+
+TEST_F(InplaceGeneratorTest, MoveFullOpsToBackTest) {
+  Graph graph(4);
+  graph[0].file_name = "A";
+  graph[0].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+  graph[1].file_name = "B";
+  graph[1].op.set_type(DeltaArchiveManifest_InstallOperation_Type_BSDIFF);
+  graph[2].file_name = "C";
+  graph[2].op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+  graph[3].file_name = "D";
+  graph[3].op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+
+  vector<Vertex::Index> vect(graph.size());
+
+  for (vector<Vertex::Index>::size_type i = 0; i < vect.size(); ++i) {
+    vect[i] = i;
+  }
+  InplaceGenerator::MoveFullOpsToBack(&graph, &vect);
+  EXPECT_EQ(vect.size(), graph.size());
+  EXPECT_EQ(graph[vect[0]].file_name, "B");
+  EXPECT_EQ(graph[vect[1]].file_name, "D");
+  EXPECT_EQ(graph[vect[2]].file_name, "A");
+  EXPECT_EQ(graph[vect[3]].file_name, "C");
+}
+
+TEST_F(InplaceGeneratorTest, AssignTempBlocksTest) {
+  Graph graph(9);
+  const vector<Extent> empt;  // empty
+  const string kFilename = "/foo";
+
+  // Some scratch space:
+  GenVertex(&graph[0], empt, VectOfExt(200, 1), "", OP_REPLACE);
+  GenVertex(&graph[1], empt, VectOfExt(210, 10), "", OP_REPLACE);
+  GenVertex(&graph[2], empt, VectOfExt(220, 1), "", OP_REPLACE);
+
+  // A cycle that requires 10 blocks to break:
+  GenVertex(&graph[3], VectOfExt(10, 11), VectOfExt(0, 9), "", OP_BSDIFF);
+  graph[3].out_edges[4] = EdgeWithReadDep(VectOfExt(0, 9));
+  GenVertex(&graph[4], VectOfExt(0, 9), VectOfExt(10, 11), "", OP_BSDIFF);
+  graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11));
+
+  // A cycle that requires 9 blocks to break:
+  GenVertex(&graph[5], VectOfExt(40, 11), VectOfExt(30, 10), "", OP_BSDIFF);
+  graph[5].out_edges[6] = EdgeWithReadDep(VectOfExt(30, 10));
+  GenVertex(&graph[6], VectOfExt(30, 10), VectOfExt(40, 11), "", OP_BSDIFF);
+  graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11));
+
+  // A cycle that requires 40 blocks to break (which is too many):
+  GenVertex(&graph[7],
+            VectOfExt(120, 50),
+            VectOfExt(60, 40),
+            "",
+            OP_BSDIFF);
+  graph[7].out_edges[8] = EdgeWithReadDep(VectOfExt(60, 40));
+  GenVertex(&graph[8],
+            VectOfExt(60, 40),
+            VectOfExt(120, 50),
+            kFilename,
+            OP_BSDIFF);
+  graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50));
+
+  graph_utils::DumpGraph(graph);
+
+  vector<Vertex::Index> final_order;
+
+  int fd;
+  EXPECT_TRUE(utils::MakeTempFile("AssignTempBlocksTestData.XXXXXX",
+                                  nullptr,
+                                  &fd));
+  ScopedFdCloser fd_closer(&fd);
+  off_t data_file_size = 0;
+
+  EXPECT_TRUE(InplaceGenerator::ConvertGraphToDag(&graph,
+                                                  "/dev/zero",
+                                                  fd,
+                                                  &data_file_size,
+                                                  &final_order,
+                                                  Vertex::kInvalidIndex));
+
+  Graph expected_graph(12);
+  GenVertex(&expected_graph[0], empt, VectOfExt(200, 1), "", OP_REPLACE);
+  GenVertex(&expected_graph[1], empt, VectOfExt(210, 10), "", OP_REPLACE);
+  GenVertex(&expected_graph[2], empt, VectOfExt(220, 1), "", OP_REPLACE);
+  GenVertex(&expected_graph[3],
+            VectOfExt(10, 11),
+            VectOfExt(0, 9),
+            "",
+            OP_BSDIFF);
+  expected_graph[3].out_edges[9] = EdgeWithReadDep(VectOfExt(0, 9));
+  GenVertex(&expected_graph[4],
+            VectOfExt(60, 9),
+            VectOfExt(10, 11),
+            "",
+            OP_BSDIFF);
+  expected_graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11));
+  expected_graph[4].out_edges[9] = EdgeWithWriteDep(VectOfExt(60, 9));
+  GenVertex(&expected_graph[5],
+            VectOfExt(40, 11),
+            VectOfExt(30, 10),
+            "",
+            OP_BSDIFF);
+  expected_graph[5].out_edges[10] = EdgeWithReadDep(VectOfExt(30, 10));
+
+  GenVertex(&expected_graph[6],
+            VectOfExt(60, 10),
+            VectOfExt(40, 11),
+            "",
+            OP_BSDIFF);
+  expected_graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11));
+  expected_graph[6].out_edges[10] = EdgeWithWriteDep(VectOfExt(60, 10));
+
+  GenVertex(&expected_graph[7],
+            VectOfExt(120, 50),
+            VectOfExt(60, 40),
+            "",
+            OP_BSDIFF);
+  expected_graph[7].out_edges[6] = EdgeWithReadDep(VectOfExt(60, 10));
+
+  GenVertex(&expected_graph[8], empt, VectOfExt(0, 50), "/foo", OP_REPLACE_BZ);
+  expected_graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50));
+
+  GenVertex(&expected_graph[9],
+            VectOfExt(0, 9),
+            VectOfExt(60, 9),
+            "",
+            OP_MOVE);
+
+  GenVertex(&expected_graph[10],
+            VectOfExt(30, 10),
+            VectOfExt(60, 10),
+            "",
+            OP_MOVE);
+  expected_graph[10].out_edges[4] = EdgeWithReadDep(VectOfExt(60, 9));
+
+  EXPECT_EQ(12, graph.size());
+  EXPECT_FALSE(graph.back().valid);
+  for (Graph::size_type i = 0; i < graph.size() - 1; i++) {
+    EXPECT_TRUE(graph[i].out_edges == expected_graph[i].out_edges);
+    if (i == 8) {
+      // special case
+    } else {
+      // EXPECT_TRUE(graph[i] == expected_graph[i]) << "i = " << i;
+    }
+  }
+}
+
+TEST_F(InplaceGeneratorTest, CreateScratchNodeTest) {
+  Vertex vertex;
+  InplaceGenerator::CreateScratchNode(12, 34, &vertex);
+  EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ,
+            vertex.op.type());
+  EXPECT_EQ(0, vertex.op.data_offset());
+  EXPECT_EQ(0, vertex.op.data_length());
+  EXPECT_EQ(1, vertex.op.dst_extents_size());
+  EXPECT_EQ(12, vertex.op.dst_extents(0).start_block());
+  EXPECT_EQ(34, vertex.op.dst_extents(0).num_blocks());
+}
+
+TEST_F(InplaceGeneratorTest, ApplyMapTest) {
+  vector<uint64_t> collection = {1, 2, 3, 4, 6};
+  vector<uint64_t> expected_values = {1, 2, 5, 4, 8};
+  map<uint64_t, uint64_t> value_map;
+  value_map[3] = 5;
+  value_map[6] = 8;
+  value_map[5] = 10;
+
+  InplaceGenerator::ApplyMap(&collection, value_map);
+  EXPECT_EQ(expected_values, collection);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/operations_generator.h b/payload_generator/operations_generator.h
new file mode 100644
index 0000000..31d57b3
--- /dev/null
+++ b/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/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
new file mode 100644
index 0000000..fe453a2
--- /dev/null
+++ b/payload_generator/payload_file.cc
@@ -0,0 +1,319 @@
+// 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/payload_file.h"
+
+#include <algorithm>
+
+#include "update_engine/file_writer.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_signer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const uint64_t kMajorVersionNumber = 1;
+
+static const char* kInstallOperationTypes[] = {
+  "REPLACE",
+  "REPLACE_BZ",
+  "MOVE",
+  "BSDIFF",
+  "SOURCE_COPY",
+  "SOURCE_BSDIFF"
+};
+
+struct DeltaObject {
+  DeltaObject(const string& in_name, const int in_type, const off_t in_size)
+      : name(in_name),
+        type(in_type),
+        size(in_size) {}
+  bool operator <(const DeltaObject& object) const {
+    return (size != object.size) ? (size < object.size) : (name < object.name);
+  }
+  string name;
+  int type;
+  off_t size;
+};
+
+// Writes the uint64_t passed in in host-endian to the file as big-endian.
+// Returns true on success.
+bool WriteUint64AsBigEndian(FileWriter* writer, const uint64_t value) {
+  uint64_t value_be = htobe64(value);
+  TEST_AND_RETURN_FALSE(writer->Write(&value_be, sizeof(value_be)));
+  return true;
+}
+
+}  // namespace
+
+const vector<PartitionName> PayloadFile::partition_disk_order_ = {
+  PartitionName::kRootfs,
+  PartitionName::kKernel,
+};
+
+bool PayloadFile::Init(const PayloadGenerationConfig& config) {
+  manifest_.set_minor_version(config.minor_version);
+
+  if (!config.source.ImageInfoIsEmpty())
+    *(manifest_.mutable_old_image_info()) = config.source.image_info;
+
+  if (!config.target.ImageInfoIsEmpty())
+    *(manifest_.mutable_new_image_info()) = config.target.image_info;
+
+  manifest_.set_block_size(config.block_size);
+
+  // Initialize the PartitionInfo objects if present.
+  if (!config.source.kernel.path.empty()) {
+    TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+        config.source.kernel,
+        manifest_.mutable_old_kernel_info()));
+  }
+  TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+      config.target.kernel,
+      manifest_.mutable_new_kernel_info()));
+  if (!config.source.rootfs.path.empty()) {
+    TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+        config.source.rootfs,
+        manifest_.mutable_old_rootfs_info()));
+  }
+  TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(
+      config.target.rootfs,
+      manifest_.mutable_new_rootfs_info()));
+  return true;
+}
+
+void PayloadFile::AddPartitionOperations(
+    PartitionName name,
+    const vector<AnnotatedOperation>& aops) {
+  aops_map_[name].insert(aops_map_[name].end(), aops.begin(), aops.end());
+}
+
+bool PayloadFile::WritePayload(const string& payload_file,
+                               const string& data_blobs_path,
+                               const string& private_key_path,
+                               uint64_t* medatata_size_out) {
+  // Reorder the data blobs with the 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(data_blobs_path, ordered_blobs_path));
+
+  // Copy the operations from the aops_map_ to the manifest.
+  manifest_.clear_install_operations();
+  manifest_.clear_kernel_install_operations();
+  for (PartitionName name : partition_disk_order_) {
+    for (const AnnotatedOperation& aop : aops_map_[name]) {
+      if (name == PartitionName::kKernel) {
+        *manifest_.add_kernel_install_operations() = aop.op;
+      } else {
+        *manifest_.add_install_operations() = aop.op;
+      }
+    }
+  }
+
+  // Check that install op blobs are in order.
+  uint64_t next_blob_offset = 0;
+  {
+    for (int i = 0; i < (manifest_.install_operations_size() +
+                         manifest_.kernel_install_operations_size()); i++) {
+      DeltaArchiveManifest_InstallOperation* op =
+          i < manifest_.install_operations_size() ?
+          manifest_.mutable_install_operations(i) :
+          manifest_.mutable_kernel_install_operations(
+              i - manifest_.install_operations_size());
+      if (op->has_data_offset()) {
+        if (op->data_offset() != next_blob_offset) {
+          LOG(FATAL) << "bad blob offset! " << op->data_offset() << " != "
+                     << next_blob_offset;
+        }
+        next_blob_offset += op->data_length();
+      }
+    }
+  }
+
+  // Signatures appear at the end of the blobs. Note the offset in the
+  // manifest_.
+  if (!private_key_path.empty()) {
+    uint64_t signature_blob_length = 0;
+    TEST_AND_RETURN_FALSE(
+        PayloadSigner::SignatureBlobLength(vector<string>(1, private_key_path),
+                                           &signature_blob_length));
+    AddSignatureOp(next_blob_offset, signature_blob_length, &manifest_);
+  }
+
+    // Serialize protobuf
+  string serialized_manifest;
+  TEST_AND_RETURN_FALSE(manifest_.AppendToString(&serialized_manifest));
+
+  LOG(INFO) << "Writing final delta file header...";
+  DirectFileWriter writer;
+  TEST_AND_RETURN_FALSE_ERRNO(writer.Open(payload_file.c_str(),
+                                          O_WRONLY | O_CREAT | O_TRUNC,
+                                          0644) == 0);
+  ScopedFileWriterCloser writer_closer(&writer);
+
+  // Write header
+  TEST_AND_RETURN_FALSE(writer.Write(kDeltaMagic, strlen(kDeltaMagic)));
+
+  // Write major version number
+  TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer, kMajorVersionNumber));
+
+  // Write protobuf length
+  TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer,
+                                               serialized_manifest.size()));
+
+  // Write protobuf
+  LOG(INFO) << "Writing final delta file protobuf... "
+            << serialized_manifest.size();
+  TEST_AND_RETURN_FALSE(writer.Write(serialized_manifest.data(),
+                                     serialized_manifest.size()));
+
+  // Append the data blobs
+  LOG(INFO) << "Writing final delta file data blobs...";
+  int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0);
+  ScopedFdCloser blobs_fd_closer(&blobs_fd);
+  TEST_AND_RETURN_FALSE(blobs_fd >= 0);
+  for (;;) {
+    vector<char> buf(1024 * 1024);
+    ssize_t rc = read(blobs_fd, buf.data(), buf.size());
+    if (0 == rc) {
+      // EOF
+      break;
+    }
+    TEST_AND_RETURN_FALSE_ERRNO(rc > 0);
+    TEST_AND_RETURN_FALSE(writer.Write(buf.data(), rc));
+  }
+
+  // Write signature blob.
+  if (!private_key_path.empty()) {
+    LOG(INFO) << "Signing the update...";
+    chromeos::Blob signature_blob;
+    TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(
+        payload_file,
+        vector<string>(1, private_key_path),
+        &signature_blob));
+    TEST_AND_RETURN_FALSE(writer.Write(signature_blob.data(),
+                                       signature_blob.size()));
+  }
+
+  *medatata_size_out =
+      strlen(kDeltaMagic) + 2 * sizeof(uint64_t) + serialized_manifest.size();
+  ReportPayloadUsage(*medatata_size_out);
+  return true;
+}
+
+bool PayloadFile::ReorderDataBlobs(
+    const string& data_blobs_path,
+    const string& new_data_blobs_path) {
+  int in_fd = open(data_blobs_path.c_str(), O_RDONLY, 0);
+  TEST_AND_RETURN_FALSE_ERRNO(in_fd >= 0);
+  ScopedFdCloser in_fd_closer(&in_fd);
+
+  DirectFileWriter writer;
+  TEST_AND_RETURN_FALSE(
+      writer.Open(new_data_blobs_path.c_str(),
+                  O_WRONLY | O_TRUNC | O_CREAT,
+                  0644) == 0);
+  ScopedFileWriterCloser writer_closer(&writer);
+  uint64_t out_file_size = 0;
+
+  for (PartitionName name : partition_disk_order_) {
+    for (AnnotatedOperation& aop : aops_map_[name]) {
+      if (!aop.op.has_data_offset())
+        continue;
+      CHECK(aop.op.has_data_length());
+      chromeos::Blob buf(aop.op.data_length());
+      ssize_t rc = pread(in_fd, buf.data(), buf.size(), aop.op.data_offset());
+      TEST_AND_RETURN_FALSE(rc == static_cast<ssize_t>(buf.size()));
+
+      // Add the hash of the data blobs for this operation
+      TEST_AND_RETURN_FALSE(AddOperationHash(&aop.op, buf));
+
+      aop.op.set_data_offset(out_file_size);
+      TEST_AND_RETURN_FALSE(writer.Write(buf.data(), buf.size()));
+      out_file_size += buf.size();
+    }
+  }
+  return true;
+}
+
+bool PayloadFile::AddOperationHash(
+    DeltaArchiveManifest_InstallOperation* op,
+    const chromeos::Blob& buf) {
+  OmahaHashCalculator hasher;
+  TEST_AND_RETURN_FALSE(hasher.Update(buf.data(), buf.size()));
+  TEST_AND_RETURN_FALSE(hasher.Finalize());
+  const chromeos::Blob& hash = hasher.raw_hash();
+  op->set_data_sha256_hash(hash.data(), hash.size());
+  return true;
+}
+
+void PayloadFile::ReportPayloadUsage(uint64_t metadata_size) const {
+  vector<DeltaObject> objects;
+  off_t total_size = 0;
+
+  for (PartitionName name : partition_disk_order_) {
+    const auto& partition_aops = aops_map_.find(name);
+    if (partition_aops == aops_map_.end())
+      continue;
+    for (const AnnotatedOperation& aop : partition_aops->second) {
+      objects.push_back(DeltaObject(aop.name,
+                                    aop.op.type(),
+                                    aop.op.data_length()));
+      total_size += aop.op.data_length();
+    }
+  }
+
+  objects.push_back(DeltaObject("<manifest-metadata>",
+                                -1,
+                                metadata_size));
+  total_size += metadata_size;
+
+  std::sort(objects.begin(), objects.end());
+
+  static const char kFormatString[] = "%6.2f%% %10jd %-10s %s\n";
+  for (const DeltaObject& object : objects) {
+    fprintf(stderr, kFormatString,
+            object.size * 100.0 / total_size,
+            static_cast<intmax_t>(object.size),
+            object.type >= 0 ? kInstallOperationTypes[object.type] : "-",
+            object.name.c_str());
+  }
+  fprintf(stderr, kFormatString,
+          100.0, static_cast<intmax_t>(total_size), "", "<total>");
+}
+
+void AddSignatureOp(uint64_t signature_blob_offset,
+                    uint64_t signature_blob_length,
+                    DeltaArchiveManifest* manifest) {
+  LOG(INFO) << "Making room for signature in file";
+  manifest->set_signatures_offset(signature_blob_offset);
+  LOG(INFO) << "set? " << manifest->has_signatures_offset();
+  // Add a dummy op at the end to appease older clients
+  DeltaArchiveManifest_InstallOperation* dummy_op =
+      manifest->add_kernel_install_operations();
+  dummy_op->set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
+  dummy_op->set_data_offset(signature_blob_offset);
+  manifest->set_signatures_offset(signature_blob_offset);
+  dummy_op->set_data_length(signature_blob_length);
+  manifest->set_signatures_size(signature_blob_length);
+  Extent* dummy_extent = dummy_op->add_dst_extents();
+  // Tell the dummy op to write this data to a big sparse hole
+  dummy_extent->set_start_block(kSparseHole);
+  dummy_extent->set_num_blocks((signature_blob_length + kBlockSize - 1) /
+                               kBlockSize);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/payload_file.h b/payload_generator/payload_file.h
new file mode 100644
index 0000000..150b0b0
--- /dev/null
+++ b/payload_generator/payload_file.h
@@ -0,0 +1,86 @@
+// 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_PAYLOAD_FILE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// Class to handle the creation of a payload file. This class is the only one
+// dealing with writing the payload and its format, but has no logic about what
+// should be on it.
+class PayloadFile {
+ public:
+  // Initialize the payload file with the payload generation config. It computes
+  // required hashes of the requested partitions.
+  bool Init(const PayloadGenerationConfig& config);
+
+  // Sets the list of operations to the payload manifest. The operations
+  // reference a blob stored in the file provided to WritePayload().
+  void AddPartitionOperations(PartitionName name,
+                              const std::vector<AnnotatedOperation>& aops);
+
+  // Write the payload to the |payload_file| file. The operations reference
+  // blobs in the |data_blobs_path| file and the blobs will be reordered in the
+  // payload file to match the order of the operations. The size of the metadata
+  // section of the payload is stored in |metadata_size_out|.
+  bool WritePayload(const std::string& payload_file,
+                    const std::string& data_blobs_path,
+                    const std::string& private_key_path,
+                    uint64_t* medatata_size_out);
+
+ private:
+  FRIEND_TEST(PayloadFileTest, ReorderBlobsTest);
+
+  // Computes a SHA256 hash of the given buf and sets the hash value in the
+  // operation so that update_engine could verify. This hash should be set
+  // for all operations that have a non-zero data blob. One exception is the
+  // dummy operation for signature blob because the contents of the signature
+  // blob will not be available at payload creation time. So, update_engine will
+  // gracefully ignore the dummy signature operation.
+  static bool AddOperationHash(DeltaArchiveManifest_InstallOperation* op,
+                               const chromeos::Blob& buf);
+
+  // Install operations in the manifest may reference data blobs, which
+  // are in data_blobs_path. This function creates a new data blobs file
+  // with the data blobs in the same order as the referencing install
+  // operations in the manifest. E.g. if manifest[0] has a data blob
+  // "X" at offset 1, manifest[1] has a data blob "Y" at offset 0,
+  // and data_blobs_path's file contains "YX", new_data_blobs_path
+  // will set to be a file that contains "XY".
+  bool ReorderDataBlobs(const std::string& data_blobs_path,
+                        const std::string& new_data_blobs_path);
+
+  // Print in stderr the Payload usage report.
+  void ReportPayloadUsage(uint64_t metadata_size) const;
+
+  // The list of partitions in the order their blobs should appear in the
+  // payload file.
+  static const std::vector<PartitionName> partition_disk_order_;
+
+  DeltaArchiveManifest manifest_;
+
+  std::map<PartitionName, std::vector<AnnotatedOperation>> aops_map_;
+};
+
+// Adds a dummy operation that points to a signature blob located at the
+// specified offset/length.
+void AddSignatureOp(uint64_t signature_blob_offset,
+                    uint64_t signature_blob_length,
+                    DeltaArchiveManifest* manifest);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
diff --git a/payload_generator/payload_file_unittest.cc b/payload_generator/payload_file_unittest.cc
new file mode 100644
index 0000000..1d9661f
--- /dev/null
+++ b/payload_generator/payload_file_unittest.cc
@@ -0,0 +1,84 @@
+// 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/payload_file.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/test_utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PayloadFileTest : public ::testing::Test {
+ protected:
+  PayloadFile payload_;
+};
+
+TEST_F(PayloadFileTest, ReorderBlobsTest) {
+  string orig_blobs;
+  EXPECT_TRUE(utils::MakeTempFile("ReorderBlobsTest.orig.XXXXXX", &orig_blobs,
+                                  nullptr));
+  ScopedPathUnlinker orig_blobs_unlinker(orig_blobs);
+
+  // The operations have three blob and one gap (the whitespace):
+  // Rootfs operation 1: [8, 3] bcd
+  // Rootfs operation 2: [7, 1] a
+  // Kernel operation 1: [0, 6] kernel
+  string orig_data = "kernel abcd";
+  EXPECT_TRUE(
+      utils::WriteFile(orig_blobs.c_str(), orig_data.data(), orig_data.size()));
+
+  string new_blobs;
+  EXPECT_TRUE(
+      utils::MakeTempFile("ReorderBlobsTest.new.XXXXXX", &new_blobs, nullptr));
+  ScopedPathUnlinker new_blobs_unlinker(new_blobs);
+
+  vector<AnnotatedOperation> aops;
+  AnnotatedOperation aop;
+  aop.op.set_data_offset(8);
+  aop.op.set_data_length(3);
+  aops.push_back(aop);
+
+  aop.op.set_data_offset(7);
+  aop.op.set_data_length(1);
+  aops.push_back(aop);
+  payload_.AddPartitionOperations(PartitionName::kRootfs, aops);
+
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(6);
+  aops = {aop};
+  payload_.AddPartitionOperations(PartitionName::kKernel, aops);
+
+  EXPECT_TRUE(payload_.ReorderDataBlobs(orig_blobs, new_blobs));
+
+  const vector<AnnotatedOperation>& rootfs_aops =
+      payload_.aops_map_[PartitionName::kRootfs];
+  const vector<AnnotatedOperation>& kernel_aops =
+      payload_.aops_map_[PartitionName::kKernel];
+  string new_data;
+  EXPECT_TRUE(utils::ReadFile(new_blobs, &new_data));
+  // Kernel blobs should appear at the end.
+  EXPECT_EQ("bcdakernel", new_data);
+
+  EXPECT_EQ(2, rootfs_aops.size());
+  EXPECT_EQ(0, rootfs_aops[0].op.data_offset());
+  EXPECT_EQ(3, rootfs_aops[0].op.data_length());
+  EXPECT_EQ(3, rootfs_aops[1].op.data_offset());
+  EXPECT_EQ(1, rootfs_aops[1].op.data_length());
+
+  EXPECT_EQ(1, kernel_aops.size());
+  EXPECT_EQ(4, kernel_aops[0].op.data_offset());
+  EXPECT_EQ(6, kernel_aops[0].op.data_length());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
new file mode 100644
index 0000000..d58c9a1
--- /dev/null
+++ b/payload_generator/payload_generation_config.cc
@@ -0,0 +1,152 @@
+// 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/payload_generation_config.h"
+
+#include <base/logging.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/ext2_filesystem.h"
+#include "update_engine/payload_generator/raw_filesystem.h"
+#include "update_engine/payload_generator/verity_utils.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+bool PartitionConfig::ValidateExists() const {
+  TEST_AND_RETURN_FALSE(!path.empty());
+  TEST_AND_RETURN_FALSE(utils::FileExists(path.c_str()));
+  TEST_AND_RETURN_FALSE(size > 0);
+  // The requested size is within the limits of the file.
+  TEST_AND_RETURN_FALSE(static_cast<off_t>(size) <=
+                        utils::FileSize(path.c_str()));
+  return true;
+}
+
+bool PartitionConfig::OpenFilesystem() {
+  if (path.empty())
+    return true;
+  fs_interface.reset();
+  if (name == PartitionName::kRootfs) {
+    fs_interface = Ext2Filesystem::CreateFromFile(path);
+  }
+
+  if (!fs_interface) {
+    // Fall back to a RAW filesystem.
+    TEST_AND_RETURN_FALSE(size % kBlockSize == 0);
+    std::string str_name = "other";
+    switch (name) {
+      case PartitionName::kKernel:
+        str_name = "kernel";
+        break;
+      case PartitionName::kRootfs:
+        str_name = "rootfs";
+        break;
+    }
+    fs_interface = RawFilesystem::Create(
+      "<" + str_name + "-partition>",
+      kBlockSize,
+      size / kBlockSize);
+  }
+  return true;
+}
+
+bool ImageConfig::ValidateIsEmpty() const {
+  TEST_AND_RETURN_FALSE(ImageInfoIsEmpty());
+
+  TEST_AND_RETURN_FALSE(rootfs.path.empty());
+  TEST_AND_RETURN_FALSE(rootfs.size == 0);
+  TEST_AND_RETURN_FALSE(kernel.path.empty());
+  TEST_AND_RETURN_FALSE(kernel.size == 0);
+  return true;
+}
+
+bool ImageConfig::LoadImageSize() {
+  TEST_AND_RETURN_FALSE(!rootfs.path.empty());
+  int rootfs_block_count, rootfs_block_size;
+  TEST_AND_RETURN_FALSE(utils::GetFilesystemSize(rootfs.path,
+                                                 &rootfs_block_count,
+                                                 &rootfs_block_size));
+  rootfs.size = static_cast<uint64_t>(rootfs_block_count) * rootfs_block_size;
+  if (!kernel.path.empty())
+    kernel.size = utils::FileSize(kernel.path);
+
+  // TODO(deymo): The delta generator algorithm doesn't support a block size
+  // different than 4 KiB. Remove this check once that's fixed. crbug.com/455045
+  if (rootfs_block_size != 4096) {
+    LOG(ERROR) << "The filesystem provided in " << rootfs.path
+               << " has a block size of " << rootfs_block_size
+               << " but delta_generator only supports 4096.";
+    return false;
+  }
+  return true;
+}
+
+bool ImageConfig::LoadVerityRootfsSize() {
+  if (kernel.path.empty())
+    return false;
+  uint64_t verity_rootfs_size = 0;
+  if (!GetVerityRootfsSize(kernel.path, &verity_rootfs_size)) {
+    LOG(INFO) << "Couldn't find verity options in source kernel config, will "
+              << "use the rootfs filesystem size instead: " << rootfs.size;
+    return false;
+  }
+  if (rootfs.size != verity_rootfs_size) {
+    LOG(WARNING) << "Using the rootfs size found in the kernel config ("
+                 << verity_rootfs_size << ") instead of the rootfs filesystem "
+                 << " size (" << rootfs.size << ").";
+    rootfs.size = verity_rootfs_size;
+  }
+  return true;
+}
+
+bool ImageConfig::ImageInfoIsEmpty() const {
+  return image_info.board().empty()
+    && image_info.key().empty()
+    && image_info.channel().empty()
+    && image_info.version().empty()
+    && image_info.build_channel().empty()
+    && image_info.build_version().empty();
+}
+
+bool PayloadGenerationConfig::Validate() const {
+  if (is_delta) {
+    TEST_AND_RETURN_FALSE(source.rootfs.ValidateExists());
+    TEST_AND_RETURN_FALSE(source.rootfs.size % block_size == 0);
+
+    if (!source.kernel.path.empty()) {
+      TEST_AND_RETURN_FALSE(source.kernel.ValidateExists());
+      TEST_AND_RETURN_FALSE(source.kernel.size % block_size == 0);
+    }
+
+    // Check for the supported minor_version values.
+    TEST_AND_RETURN_FALSE(minor_version == kInPlaceMinorPayloadVersion ||
+                          minor_version == kSourceMinorPayloadVersion);
+
+    // If new_image_info is present, old_image_info must be present.
+    TEST_AND_RETURN_FALSE(source.ImageInfoIsEmpty() ==
+                          target.ImageInfoIsEmpty());
+  } else {
+    // All the "source" image fields must be empty for full payloads.
+    TEST_AND_RETURN_FALSE(source.ValidateIsEmpty());
+    TEST_AND_RETURN_FALSE(minor_version ==
+                          DeltaPerformer::kFullPayloadMinorVersion);
+  }
+
+  // In all cases, the target image must exists.
+  TEST_AND_RETURN_FALSE(target.rootfs.ValidateExists());
+  TEST_AND_RETURN_FALSE(target.kernel.ValidateExists());
+  TEST_AND_RETURN_FALSE(target.rootfs.size % block_size == 0);
+  TEST_AND_RETURN_FALSE(target.kernel.size % block_size == 0);
+
+  TEST_AND_RETURN_FALSE(chunk_size == -1 || chunk_size % block_size == 0);
+
+  TEST_AND_RETURN_FALSE(rootfs_partition_size % block_size == 0);
+  TEST_AND_RETURN_FALSE(rootfs_partition_size >= target.rootfs.size);
+
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
new file mode 100644
index 0000000..f18055f
--- /dev/null
+++ b/payload_generator/payload_generation_config.h
@@ -0,0 +1,128 @@
+// 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_PAYLOAD_GENERATION_CONFIG_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
+
+#include <cstddef>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The list different kind of partitions supported by the updater.
+enum class PartitionName {
+  kKernel,
+  kRootfs,
+};
+
+struct PartitionConfig {
+  explicit PartitionConfig(PartitionName name) : name(name) {}
+
+  // Returns whether the PartitionConfig is not an empty image and all the
+  // fields are set correctly to a valid image file.
+  bool ValidateExists() const;
+
+  // Open then filesystem stored in this partition and stores it in
+  // |fs_interface|. Returns whether opening the filesystem worked.
+  bool OpenFilesystem();
+
+  // The path to the partition file. This can be a regular file or a block
+  // device such as a loop device.
+  std::string path;
+
+  // The size of the data in |path|. If rootfs verification is used (verity)
+  // this value should match the size of the verity device for the rootfs, and
+  // the size of the whole kernel. This value could be smaller than the
+  // partition and is the size of the data update_engine assumes verified for
+  // the source image, and the size of that data it should generate for the
+  // target image.
+  uint64_t size = 0;
+
+  // The FilesystemInterface implementation used to access this partition's
+  // files.
+  std::unique_ptr<FilesystemInterface> fs_interface;
+
+  PartitionName name;
+};
+
+// The ImageConfig struct describes a pair of binaries kernel and rootfs and the
+// metadata associated with the image they are part of, like build number, size,
+// etc.
+struct ImageConfig {
+  // Returns whether the ImageConfig is an empty image.
+  bool ValidateIsEmpty() const;
+
+  // Load |rootfs_size| and |kernel.size| from the respective image files. For
+  // the kernel, the whole |kernel.path| file is assumed. For the rootfs, the
+  // size is detected from the filesystem.
+  // Returns whether the image size was properly detected.
+  bool LoadImageSize();
+
+  // Load the |rootfs_size| stored in the kernel command line in the
+  // |kernel.path| when the kernel is using rootfs verification (dm-verity).
+  // Returns whether it loaded the size from the kernel command line. For
+  // example, it would return false if no |kernel.path| was provided or the
+  // kernel doesn't have verity enabled.
+  bool LoadVerityRootfsSize();
+
+  // Returns whether the |image_info| field is empty.
+  bool ImageInfoIsEmpty() const;
+
+  // The ImageInfo message defined in the update_metadata.proto file describes
+  // the metadata of the image.
+  ImageInfo image_info;
+
+  // The updated partitions.
+  PartitionConfig rootfs = PartitionConfig{PartitionName::kRootfs};
+  PartitionConfig kernel = PartitionConfig{PartitionName::kKernel};
+};
+
+// The PayloadGenerationConfig struct encapsulates all the configuration to
+// build the requested payload. This includes information about the old and new
+// image as well as the restrictions applied to the payload (like minor-version
+// and full/delta payload).
+struct PayloadGenerationConfig {
+  // Returns whether the PayloadGenerationConfig is valid.
+  bool Validate() const;
+
+  // Image information about the new image that's the target of this payload.
+  ImageConfig target;
+
+  // Image information pertaining the old image, if any. This is only valid
+  // if is_full is false, so we are requested a delta payload.
+  ImageConfig source;
+
+  // Wheter the requested payload is a delta payload.
+  bool is_delta = false;
+
+  // The minor_version of the requested payload.
+  uint32_t minor_version;
+
+  // The size of the rootfs partition, that not necessarily is the same as the
+  // filesystem in either source or target version, since there is some space
+  // after the partition used to store the verity hashes and or the bootcache.
+  uint64_t rootfs_partition_size = 0;
+
+  // The chunk size is the maximum size that a single operation should write in
+  // the destination. Operations bigger than chunk_size should be split. A value
+  // of -1 means no chunk_size limit.
+  off_t chunk_size = -1;
+
+  // TODO(deymo): Remove the block_size member and maybe replace it with a
+  // minimum alignment size for blocks (if needed). Algorithms should be able to
+  // pick the block_size they want, but for now only 4 KiB is supported.
+
+  // The block size used for all the operations in the manifest.
+  size_t block_size = 4096;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc
new file mode 100644
index 0000000..8c3bc28
--- /dev/null
+++ b/payload_generator/payload_signer.cc
@@ -0,0 +1,322 @@
+// Copyright (c) 2011 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/payload_signer.h"
+
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <chromeos/data_encoding.h>
+#include <openssl/pem.h>
+
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/payload_verifier.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Given raw |signatures|, packs them into a protobuf and serializes it into a
+// binary blob. Returns true on success, false otherwise.
+bool ConvertSignatureToProtobufBlob(const vector<chromeos::Blob>& signatures,
+                                    chromeos::Blob* out_signature_blob) {
+  // Pack it into a protobuf
+  Signatures out_message;
+  uint32_t version = kSignatureMessageOriginalVersion;
+  LOG_IF(WARNING, kSignatureMessageCurrentVersion -
+         kSignatureMessageOriginalVersion + 1 < signatures.size())
+      << "You may want to support clients in the range ["
+      << kSignatureMessageOriginalVersion << ", "
+      << kSignatureMessageCurrentVersion << "] inclusive, but you only "
+      << "provided " << signatures.size() << " signatures.";
+  for (const chromeos::Blob& signature : signatures) {
+    Signatures_Signature* sig_message = out_message.add_signatures();
+    sig_message->set_version(version++);
+    sig_message->set_data(signature.data(), signature.size());
+  }
+
+  // Serialize protobuf
+  string serialized;
+  TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized));
+  out_signature_blob->insert(out_signature_blob->end(),
+                             serialized.begin(),
+                             serialized.end());
+  LOG(INFO) << "Signature blob size: " << out_signature_blob->size();
+  return true;
+}
+
+// Given an unsigned payload under |payload_path| and the |signature_blob_size|
+// generates an updated payload that includes a dummy signature op in its
+// manifest. It populates |out_metadata_size| with the size of the final
+// manifest after adding the dummy signature operation, and
+// |out_signatures_offset| with the expected offset for the new blob. Returns
+// true on success, false otherwise.
+bool AddSignatureOpToPayload(const string& payload_path,
+                             uint64_t signature_blob_size,
+                             chromeos::Blob* out_payload,
+                             uint64_t* out_metadata_size,
+                             uint64_t* out_signatures_offset) {
+  const int kProtobufOffset = 20;
+  const int kProtobufSizeOffset = 12;
+
+  // Loads the payload.
+  chromeos::Blob payload;
+  DeltaArchiveManifest manifest;
+  uint64_t metadata_size;
+  TEST_AND_RETURN_FALSE(PayloadVerifier::LoadPayload(
+      payload_path, &payload, &manifest, &metadata_size));
+
+  // Is there already a signature op in place?
+  if (manifest.has_signatures_size()) {
+    // The signature op is tied to the size of the signature blob, but not it's
+    // contents. We don't allow the manifest to change if there is already an op
+    // present, because that might invalidate previously generated
+    // hashes/signatures.
+    if (manifest.signatures_size() != signature_blob_size) {
+      LOG(ERROR) << "Attempt to insert different signature sized blob. "
+                 << "(current:" << manifest.signatures_size()
+                 << "new:" << signature_blob_size << ")";
+      return false;
+    }
+
+    LOG(INFO) << "Matching signature sizes already present.";
+  } else {
+    // Updates the manifest to include the signature operation.
+    AddSignatureOp(payload.size() - metadata_size,
+                   signature_blob_size,
+                   &manifest);
+
+    // Updates the payload to include the new manifest.
+    string serialized_manifest;
+    TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest));
+    LOG(INFO) << "Updated protobuf size: " << serialized_manifest.size();
+    payload.erase(payload.begin() + kProtobufOffset,
+                  payload.begin() + metadata_size);
+    payload.insert(payload.begin() + kProtobufOffset,
+                   serialized_manifest.begin(),
+                   serialized_manifest.end());
+
+    // Updates the protobuf size.
+    uint64_t size_be = htobe64(serialized_manifest.size());
+    memcpy(&payload[kProtobufSizeOffset], &size_be, sizeof(size_be));
+    metadata_size = serialized_manifest.size() + kProtobufOffset;
+
+    LOG(INFO) << "Updated payload size: " << payload.size();
+    LOG(INFO) << "Updated metadata size: " << metadata_size;
+  }
+
+  out_payload->swap(payload);
+  *out_metadata_size = metadata_size;
+  *out_signatures_offset = metadata_size + manifest.signatures_offset();
+  LOG(INFO) << "Signature Blob Offset: " << *out_signatures_offset;
+  return true;
+}
+}  // namespace
+
+bool PayloadSigner::SignHash(const chromeos::Blob& hash,
+                             const string& private_key_path,
+                             chromeos::Blob* out_signature) {
+  LOG(INFO) << "Signing hash with private key: " << private_key_path;
+  string sig_path;
+  TEST_AND_RETURN_FALSE(
+      utils::MakeTempFile("signature.XXXXXX", &sig_path, nullptr));
+  ScopedPathUnlinker sig_path_unlinker(sig_path);
+
+  string hash_path;
+  TEST_AND_RETURN_FALSE(
+      utils::MakeTempFile("hash.XXXXXX", &hash_path, nullptr));
+  ScopedPathUnlinker hash_path_unlinker(hash_path);
+  // We expect unpadded SHA256 hash coming in
+  TEST_AND_RETURN_FALSE(hash.size() == 32);
+  chromeos::Blob padded_hash(hash);
+  PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash);
+  TEST_AND_RETURN_FALSE(utils::WriteFile(hash_path.c_str(),
+                                         padded_hash.data(),
+                                         padded_hash.size()));
+
+  // This runs on the server, so it's okay to cop out and call openssl
+  // executable rather than properly use the library
+  vector<string> cmd;
+  base::SplitString("openssl rsautl -raw -sign -inkey x -in x -out x",
+                    ' ',
+                    &cmd);
+  cmd[cmd.size() - 5] = private_key_path;
+  cmd[cmd.size() - 3] = hash_path;
+  cmd[cmd.size() - 1] = sig_path;
+
+  int return_code = 0;
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code,
+                                                    nullptr));
+  TEST_AND_RETURN_FALSE(return_code == 0);
+
+  chromeos::Blob signature;
+  TEST_AND_RETURN_FALSE(utils::ReadFile(sig_path, &signature));
+  out_signature->swap(signature);
+  return true;
+}
+
+bool PayloadSigner::SignPayload(const string& unsigned_payload_path,
+                                const vector<string>& private_key_paths,
+                                chromeos::Blob* out_signature_blob) {
+  chromeos::Blob hash_data;
+  TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfFile(
+      unsigned_payload_path, -1, &hash_data) ==
+                        utils::FileSize(unsigned_payload_path));
+
+  vector<chromeos::Blob> signatures;
+  for (const string& path : private_key_paths) {
+    chromeos::Blob signature;
+    TEST_AND_RETURN_FALSE(SignHash(hash_data, path, &signature));
+    signatures.push_back(signature);
+  }
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+                                                       out_signature_blob));
+  return true;
+}
+
+bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths,
+                                        uint64_t* out_length) {
+  DCHECK(out_length);
+
+  string x_path;
+  TEST_AND_RETURN_FALSE(
+      utils::MakeTempFile("signed_data.XXXXXX", &x_path, nullptr));
+  ScopedPathUnlinker x_path_unlinker(x_path);
+  TEST_AND_RETURN_FALSE(utils::WriteFile(x_path.c_str(), "x", 1));
+
+  chromeos::Blob sig_blob;
+  TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(x_path,
+                                                   private_key_paths,
+                                                   &sig_blob));
+  *out_length = sig_blob.size();
+  return true;
+}
+
+bool PayloadSigner::PrepPayloadForHashing(
+        const string& payload_path,
+        const vector<int>& signature_sizes,
+        chromeos::Blob* payload_out,
+        uint64_t* metadata_size_out,
+        uint64_t* signatures_offset_out) {
+  // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
+
+  // Loads the payload and adds the signature op to it.
+  vector<chromeos::Blob> signatures;
+  for (int signature_size : signature_sizes) {
+    signatures.emplace_back(signature_size, 0);
+  }
+  chromeos::Blob signature_blob;
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+                                                       &signature_blob));
+  TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
+                                                signature_blob.size(),
+                                                payload_out,
+                                                metadata_size_out,
+                                                signatures_offset_out));
+
+  return true;
+}
+
+bool PayloadSigner::HashPayloadForSigning(const string& payload_path,
+                                          const vector<int>& signature_sizes,
+                                          chromeos::Blob* out_hash_data) {
+  chromeos::Blob payload;
+  uint64_t metadata_size;
+  uint64_t signatures_offset;
+
+  TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path,
+                                              signature_sizes,
+                                              &payload,
+                                              &metadata_size,
+                                              &signatures_offset));
+
+  // Calculates the hash on the updated payload. Note that we stop calculating
+  // before we reach the signature information.
+  TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(payload.data(),
+                                                            signatures_offset,
+                                                            out_hash_data));
+  return true;
+}
+
+bool PayloadSigner::HashMetadataForSigning(const string& payload_path,
+                                           const vector<int>& signature_sizes,
+                                           chromeos::Blob* out_metadata_hash) {
+  chromeos::Blob payload;
+  uint64_t metadata_size;
+  uint64_t signatures_offset;
+
+  TEST_AND_RETURN_FALSE(PrepPayloadForHashing(payload_path,
+                                              signature_sizes,
+                                              &payload,
+                                              &metadata_size,
+                                              &signatures_offset));
+
+  // Calculates the hash on the manifest.
+  TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(payload.data(),
+                                                            metadata_size,
+                                                            out_metadata_hash));
+  return true;
+}
+
+bool PayloadSigner::AddSignatureToPayload(
+    const string& payload_path,
+    const vector<chromeos::Blob>& signatures,
+    const string& signed_payload_path,
+    uint64_t *out_metadata_size) {
+  // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
+
+  // Loads the payload and adds the signature op to it.
+  chromeos::Blob signature_blob;
+  TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+                                                       &signature_blob));
+  chromeos::Blob payload;
+  uint64_t signatures_offset;
+  TEST_AND_RETURN_FALSE(AddSignatureOpToPayload(payload_path,
+                                                signature_blob.size(),
+                                                &payload,
+                                                out_metadata_size,
+                                                &signatures_offset));
+  // Appends the signature blob to the end of the payload and writes the new
+  // payload.
+  LOG(INFO) << "Payload size before signatures: " << payload.size();
+  payload.resize(signatures_offset);
+  payload.insert(payload.begin() + signatures_offset,
+                 signature_blob.begin(),
+                 signature_blob.end());
+  LOG(INFO) << "Signed payload size: " << payload.size();
+  TEST_AND_RETURN_FALSE(utils::WriteFile(signed_payload_path.c_str(),
+                                         payload.data(),
+                                         payload.size()));
+  return true;
+}
+
+bool PayloadSigner::GetMetadataSignature(const void* const metadata,
+                                         size_t metadata_size,
+                                         const string& private_key_path,
+                                         string* out_signature) {
+  // Calculates the hash on the updated payload. Note that the payload includes
+  // the signature op but doesn't include the signature blob at the end.
+  chromeos::Blob metadata_hash;
+  TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(metadata,
+                                                            metadata_size,
+                                                            &metadata_hash));
+
+  chromeos::Blob signature;
+  TEST_AND_RETURN_FALSE(SignHash(metadata_hash,
+                                 private_key_path,
+                                 &signature));
+
+  *out_signature = chromeos::data_encoding::Base64Encode(signature);
+  return true;
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/payload_signer.h b/payload_generator/payload_signer.h
new file mode 100644
index 0000000..a17a984
--- /dev/null
+++ b/payload_generator/payload_signer.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2010 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_PAYLOAD_SIGNER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signing.
+// See update_metadata.proto for more info.
+
+namespace chromeos_update_engine {
+
+class PayloadSigner {
+ public:
+  // Given a raw |hash| and a private key in |private_key_path| calculates the
+  // raw signature in |out_signature|. Returns true on success, false otherwise.
+  static bool SignHash(const chromeos::Blob& hash,
+                       const std::string& private_key_path,
+                       chromeos::Blob* out_signature);
+
+  // Given an unsigned payload in |unsigned_payload_path| and private keys in
+  // |private_key_path|, calculates the signature blob into
+  // |out_signature_blob|. Note that the payload must already have an updated
+  // manifest that includes the dummy signature op. Returns true on success,
+  // false otherwise.
+  static bool SignPayload(const std::string& unsigned_payload_path,
+                          const std::vector<std::string>& private_key_paths,
+                          chromeos::Blob* out_signature_blob);
+
+  // Returns the length of out_signature_blob that will result in a call
+  // to SignPayload with the given private keys. Returns true on success.
+  static bool SignatureBlobLength(
+      const std::vector<std::string>& private_key_paths,
+      uint64_t* out_length);
+
+  // This is a helper method for HashPayloadforSigning and
+  // HashMetadataForSigning. It loads the payload into memory, and inserts
+  // signature placeholders if Signatures aren't already present.
+  static bool PrepPayloadForHashing(
+        const std::string& payload_path,
+        const std::vector<int>& signature_sizes,
+        chromeos::Blob* payload_out,
+        uint64_t* metadata_size_out,
+        uint64_t* signatures_offset_out);
+
+  // Given an unsigned payload in |payload_path|,
+  // this method does two things:
+  // 1. Uses PrepPayloadForHashing to inserts placeholder signature operations
+  //    to make the manifest match what the final signed payload will look
+  //    like based on |signatures_sizes|, if needed.
+  // 2. It calculates the raw SHA256 hash of the payload in |payload_path|
+  //    (except signatures) and returns the result in |out_hash_data|.
+  //
+  // The dummy signatures are not preserved or written to disk.
+  static bool HashPayloadForSigning(const std::string& payload_path,
+                                    const std::vector<int>& signature_sizes,
+                                    chromeos::Blob* out_hash_data);
+
+  // Given an unsigned payload in |payload_path|,
+  // this method does two things:
+  // 1. Uses PrepPayloadForHashing to inserts placeholder signature operations
+  //    to make the manifest match what the final signed payload will look
+  //    like based on |signatures_sizes|, if needed.
+  // 2. It calculates the raw SHA256 hash of the metadata from the payload in
+  //    |payload_path| (except signatures) and returns the result in
+  //    |out_metadata_hash|.
+  //
+  // The dummy signatures are not preserved or written to disk.
+  static bool HashMetadataForSigning(const std::string& payload_path,
+                                     const std::vector<int>& signature_sizes,
+                                     chromeos::Blob* out_metadata_hash);
+
+  // Given an unsigned payload in |payload_path| (with no dummy signature op)
+  // and the raw |signatures| updates the payload to include the signature thus
+  // turning it into a signed payload. The new payload is stored in
+  // |signed_payload_path|. |payload_path| and |signed_payload_path| can point
+  // to the same file. Populates |out_metadata_size| with the size of the
+  // metadata after adding the signature operation in the manifest.Returns true
+  // on success, false otherwise.
+  static bool AddSignatureToPayload(
+      const std::string& payload_path,
+      const std::vector<chromeos::Blob>& signatures,
+      const std::string& signed_payload_path,
+      uint64_t* out_metadata_size);
+
+  // Computes the SHA256 hash of the first metadata_size bytes of |metadata|
+  // and signs the hash with the given private_key_path and writes the signed
+  // hash in |out_signature|. Returns true if successful or false if there was
+  // any error in the computations.
+  static bool GetMetadataSignature(const void* const metadata,
+                                   size_t metadata_size,
+                                   const std::string& private_key_path,
+                                   std::string* out_signature);
+
+ private:
+  // This should never be constructed
+  DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadSigner);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
diff --git a/payload_generator/payload_signer_unittest.cc b/payload_generator/payload_signer_unittest.cc
new file mode 100644
index 0000000..d74cdfb
--- /dev/null
+++ b/payload_generator/payload_signer_unittest.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2010 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/payload_signer.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_verifier.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+using std::vector;
+
+// Note: the test key was generated with the following command:
+// openssl genrsa -out unittest_key.pem 2048
+// The public-key version is created by the build system.
+
+namespace chromeos_update_engine {
+
+const char* kUnittestPrivateKeyPath = "unittest_key.pem";
+const char* kUnittestPublicKeyPath = "unittest_key.pub.pem";
+const char* kUnittestPrivateKey2Path = "unittest_key2.pem";
+const char* kUnittestPublicKey2Path = "unittest_key2.pub.pem";
+
+// Some data and its corresponding hash and signature:
+const char kDataToSign[] = "This is some data to sign.";
+
+// Generated by:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+//   hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataHash[] = {
+  0x7a, 0x07, 0xa6, 0x44, 0x08, 0x86, 0x20, 0xa6,
+  0xc1, 0xf8, 0xd9, 0x02, 0x05, 0x63, 0x0d, 0xb7,
+  0xfc, 0x2b, 0xa0, 0xa9, 0x7c, 0x9d, 0x1d, 0x8c,
+  0x01, 0xf5, 0x78, 0x6d, 0xc5, 0x11, 0xb4, 0x06
+};
+
+// Generated with openssl 1.0, which at the time of this writing, you need
+// to download and install yourself. Here's my command:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+//    ~/local/bin/openssl pkeyutl -sign -inkey unittest_key.pem -pkeyopt
+//    digest:sha256 | hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataSignature[] = {
+  0x9f, 0x86, 0x25, 0x8b, 0xf3, 0xcc, 0xe3, 0x95,
+  0x5f, 0x45, 0x83, 0xb2, 0x66, 0xf0, 0x2a, 0xcf,
+  0xb7, 0xaa, 0x52, 0x25, 0x7a, 0xdd, 0x9d, 0x65,
+  0xe5, 0xd6, 0x02, 0x4b, 0x37, 0x99, 0x53, 0x06,
+  0xc2, 0xc9, 0x37, 0x36, 0x25, 0x62, 0x09, 0x4f,
+  0x6b, 0x22, 0xf8, 0xb3, 0x89, 0x14, 0x98, 0x1a,
+  0xbc, 0x30, 0x90, 0x4a, 0x43, 0xf5, 0xea, 0x2e,
+  0xf0, 0xa4, 0xba, 0xc3, 0xa7, 0xa3, 0x44, 0x70,
+  0xd6, 0xc4, 0x89, 0xd8, 0x45, 0x71, 0xbb, 0xee,
+  0x59, 0x87, 0x3d, 0xd5, 0xe5, 0x40, 0x22, 0x3d,
+  0x73, 0x7e, 0x2a, 0x58, 0x93, 0x8e, 0xcb, 0x9c,
+  0xf2, 0xbb, 0x4a, 0xc9, 0xd2, 0x2c, 0x52, 0x42,
+  0xb0, 0xd1, 0x13, 0x22, 0xa4, 0x78, 0xc7, 0xc6,
+  0x3e, 0xf1, 0xdc, 0x4c, 0x7b, 0x2d, 0x40, 0xda,
+  0x58, 0xac, 0x4a, 0x11, 0x96, 0x3d, 0xa0, 0x01,
+  0xf6, 0x96, 0x74, 0xf6, 0x6c, 0x0c, 0x49, 0x69,
+  0x4e, 0xc1, 0x7e, 0x9f, 0x2a, 0x42, 0xdd, 0x15,
+  0x6b, 0x37, 0x2e, 0x3a, 0xa7, 0xa7, 0x6d, 0x91,
+  0x13, 0xe8, 0x59, 0xde, 0xfe, 0x99, 0x07, 0xd9,
+  0x34, 0x0f, 0x17, 0xb3, 0x05, 0x4c, 0xd2, 0xc6,
+  0x82, 0xb7, 0x38, 0x36, 0x63, 0x1d, 0x9e, 0x21,
+  0xa6, 0x32, 0xef, 0xf1, 0x65, 0xe6, 0xed, 0x95,
+  0x25, 0x9b, 0x61, 0xe0, 0xba, 0x86, 0xa1, 0x7f,
+  0xf8, 0xa5, 0x4a, 0x32, 0x1f, 0x15, 0x20, 0x8a,
+  0x41, 0xc5, 0xb0, 0xd9, 0x4a, 0xda, 0x85, 0xf3,
+  0xdc, 0xa0, 0x98, 0x5d, 0x1d, 0x18, 0x9d, 0x2e,
+  0x42, 0xea, 0x69, 0x13, 0x74, 0x3c, 0x74, 0xf7,
+  0x6d, 0x43, 0xb0, 0x63, 0x90, 0xdb, 0x04, 0xd5,
+  0x05, 0xc9, 0x73, 0x1f, 0x6c, 0xd6, 0xfa, 0x46,
+  0x4e, 0x0f, 0x33, 0x58, 0x5b, 0x0d, 0x1b, 0x55,
+  0x39, 0xb9, 0x0f, 0x43, 0x37, 0xc0, 0x06, 0x0c,
+  0x29, 0x93, 0x43, 0xc7, 0x43, 0xb9, 0xab, 0x7d
+};
+
+namespace {
+void SignSampleData(chromeos::Blob* out_signature_blob) {
+  string data_path;
+  ASSERT_TRUE(
+      utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+  ScopedPathUnlinker data_path_unlinker(data_path);
+  ASSERT_TRUE(utils::WriteFile(data_path.c_str(),
+                               kDataToSign,
+                               strlen(kDataToSign)));
+  uint64_t length = 0;
+  EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+      vector<string> (1, kUnittestPrivateKeyPath),
+      &length));
+  EXPECT_GT(length, 0);
+  EXPECT_TRUE(PayloadSigner::SignPayload(
+      data_path,
+      vector<string>(1, kUnittestPrivateKeyPath),
+      out_signature_blob));
+  EXPECT_EQ(length, out_signature_blob->size());
+}
+}  // namespace
+
+TEST(PayloadSignerTest, SimpleTest) {
+  chromeos::Blob signature_blob;
+  SignSampleData(&signature_blob);
+
+  // Check the signature itself
+  Signatures signatures;
+  EXPECT_TRUE(signatures.ParseFromArray(signature_blob.data(),
+                                        signature_blob.size()));
+  EXPECT_EQ(1, signatures.signatures_size());
+  const Signatures_Signature& signature = signatures.signatures(0);
+  EXPECT_EQ(kSignatureMessageOriginalVersion, signature.version());
+  const string sig_data = signature.data();
+  ASSERT_EQ(arraysize(kDataSignature), sig_data.size());
+  for (size_t i = 0; i < arraysize(kDataSignature); i++) {
+    EXPECT_EQ(kDataSignature[i], static_cast<uint8_t>(sig_data[i]));
+  }
+}
+
+TEST(PayloadSignerTest, VerifySignatureTest) {
+  chromeos::Blob signature_blob;
+  SignSampleData(&signature_blob);
+
+  chromeos::Blob hash_data;
+  EXPECT_TRUE(PayloadVerifier::VerifySignature(signature_blob,
+                                             kUnittestPublicKeyPath,
+                                             &hash_data));
+  chromeos::Blob padded_hash_data(std::begin(kDataHash), std::end(kDataHash));
+  PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash_data);
+  ASSERT_EQ(padded_hash_data.size(), hash_data.size());
+  for (size_t i = 0; i < padded_hash_data.size(); i++) {
+    EXPECT_EQ(padded_hash_data[i], hash_data[i]);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/raw_filesystem.cc b/payload_generator/raw_filesystem.cc
new file mode 100644
index 0000000..f24096e
--- /dev/null
+++ b/payload_generator/raw_filesystem.cc
@@ -0,0 +1,41 @@
+// 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/raw_filesystem.h"
+
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_engine {
+
+unique_ptr<RawFilesystem> RawFilesystem::Create(
+      const std::string& filename, uint64_t block_size, uint64_t block_count) {
+  unique_ptr<RawFilesystem> result(new RawFilesystem());
+  result->filename_ = filename;
+  result->block_size_ = block_size;
+  result->block_count_ = block_count;
+  return result;
+}
+
+size_t RawFilesystem::GetBlockSize() const {
+  return block_size_;
+}
+
+size_t RawFilesystem::GetBlockCount() const {
+  return block_count_;
+}
+
+bool RawFilesystem::GetFiles(std::vector<File>* files) const {
+  files->clear();
+  File file;
+  file.name = filename_;
+  file.extents = { ExtentForRange(0, block_count_) };
+  files->push_back(file);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/raw_filesystem.h b/payload_generator/raw_filesystem.h
new file mode 100644
index 0000000..bd66487
--- /dev/null
+++ b/payload_generator/raw_filesystem.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_RAW_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
+
+// A simple filesystem interface implementation used for unknown filesystem
+// format such as the kernel.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class RawFilesystem : public FilesystemInterface {
+ public:
+  static std::unique_ptr<RawFilesystem> Create(
+      const std::string& filename, uint64_t block_size, uint64_t block_count);
+  virtual ~RawFilesystem() = default;
+
+  // FilesystemInterface overrides.
+  size_t GetBlockSize() const override;
+  size_t GetBlockCount() const override;
+
+  // GetFiles will return only one file with all the blocks of the filesystem
+  // with the name passed during construction.
+  bool GetFiles(std::vector<File>* files) const override;
+
+  bool LoadSettings(chromeos::KeyValueStore* store) const override {
+    return false;
+  }
+
+ private:
+  RawFilesystem() = default;
+
+  std::string filename_;
+  uint64_t block_count_;
+  uint64_t block_size_;
+
+  DISALLOW_COPY_AND_ASSIGN(RawFilesystem);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
diff --git a/payload_generator/tarjan.cc b/payload_generator/tarjan.cc
new file mode 100644
index 0000000..f3029c1
--- /dev/null
+++ b/payload_generator/tarjan.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2010 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/tarjan.h"
+
+#include <algorithm>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/utils.h"
+
+using std::min;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const vector<Vertex>::size_type kInvalidIndex = -1;
+}
+
+void TarjanAlgorithm::Execute(Vertex::Index vertex,
+                              Graph* graph,
+                              vector<Vertex::Index>* out) {
+  stack_.clear();
+  components_.clear();
+  index_ = 0;
+  for (Graph::iterator it = graph->begin(); it != graph->end(); ++it)
+    it->index = it->lowlink = kInvalidIndex;
+  required_vertex_ = vertex;
+
+  Tarjan(vertex, graph);
+  if (!components_.empty())
+    out->swap(components_[0]);
+}
+
+void TarjanAlgorithm::Tarjan(Vertex::Index vertex, Graph* graph) {
+  CHECK_EQ((*graph)[vertex].index, kInvalidIndex);
+  (*graph)[vertex].index = index_;
+  (*graph)[vertex].lowlink = index_;
+  index_++;
+  stack_.push_back(vertex);
+  for (Vertex::EdgeMap::iterator it = (*graph)[vertex].out_edges.begin();
+       it != (*graph)[vertex].out_edges.end(); ++it) {
+    Vertex::Index vertex_next = it->first;
+    if ((*graph)[vertex_next].index == kInvalidIndex) {
+      Tarjan(vertex_next, graph);
+      (*graph)[vertex].lowlink = min((*graph)[vertex].lowlink,
+                                     (*graph)[vertex_next].lowlink);
+    } else if (utils::VectorContainsValue(stack_, vertex_next)) {
+      (*graph)[vertex].lowlink = min((*graph)[vertex].lowlink,
+                                     (*graph)[vertex_next].index);
+    }
+  }
+  if ((*graph)[vertex].lowlink == (*graph)[vertex].index) {
+    vector<Vertex::Index> component;
+    Vertex::Index other_vertex;
+    do {
+      other_vertex = stack_.back();
+      stack_.pop_back();
+      component.push_back(other_vertex);
+    } while (other_vertex != vertex && !stack_.empty());
+
+    if (utils::VectorContainsValue(component, required_vertex_)) {
+      components_.resize(components_.size() + 1);
+      component.swap(components_.back());
+    }
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/tarjan.h b/payload_generator/tarjan.h
new file mode 100644
index 0000000..e3dc97f
--- /dev/null
+++ b/payload_generator/tarjan.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2010 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_TARJAN_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_
+
+// This is an implementation of Tarjan's algorithm which finds all
+// Strongly Connected Components in a graph.
+
+// Note: a true Tarjan algorithm would find all strongly connected components
+// in the graph. This implementation will only find the strongly connected
+// component containing the vertex passed in.
+
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+class TarjanAlgorithm {
+ public:
+  TarjanAlgorithm() : index_(0), required_vertex_(0) {}
+
+  // 'out' is set to the result if there is one, otherwise it's untouched.
+  void Execute(Vertex::Index vertex,
+               Graph* graph,
+               std::vector<Vertex::Index>* out);
+ private:
+  void Tarjan(Vertex::Index vertex, Graph* graph);
+
+  Vertex::Index index_;
+  Vertex::Index required_vertex_;
+  std::vector<Vertex::Index> stack_;
+  std::vector<std::vector<Vertex::Index>> components_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_TARJAN_H_
diff --git a/payload_generator/tarjan_unittest.cc b/payload_generator/tarjan_unittest.cc
new file mode 100644
index 0000000..76f5f2c
--- /dev/null
+++ b/payload_generator/tarjan_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2010 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/tarjan.h"
+
+#include <string>
+#include <utility>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/utils.h"
+
+using std::make_pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class TarjanAlgorithmTest : public ::testing::Test {};
+
+TEST(TarjanAlgorithmTest, SimpleTest) {
+  const Vertex::Index n_a = 0;
+  const Vertex::Index n_b = 1;
+  const Vertex::Index n_c = 2;
+  const Vertex::Index n_d = 3;
+  const Vertex::Index n_e = 4;
+  const Vertex::Index n_f = 5;
+  const Vertex::Index n_g = 6;
+  const Vertex::Index n_h = 7;
+  const Graph::size_type kNodeCount = 8;
+
+  Graph graph(kNodeCount);
+
+  graph[n_a].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_a].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+  graph[n_c].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_b, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_c, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_f].out_edges.insert(make_pair(n_g, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_h, EdgeProperties()));
+  graph[n_h].out_edges.insert(make_pair(n_g, EdgeProperties()));
+
+  TarjanAlgorithm tarjan;
+
+  for (Vertex::Index i = n_a; i <= n_e; i++) {
+    vector<Vertex::Index> vertex_indexes;
+    tarjan.Execute(i, &graph, &vertex_indexes);
+
+    EXPECT_EQ(5, vertex_indexes.size());
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_a));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_b));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_c));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_d));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_e));
+  }
+
+  {
+    vector<Vertex::Index> vertex_indexes;
+    tarjan.Execute(n_f, &graph, &vertex_indexes);
+
+    EXPECT_EQ(1, vertex_indexes.size());
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_f));
+  }
+
+  for (Vertex::Index i = n_g; i <= n_h; i++) {
+    vector<Vertex::Index> vertex_indexes;
+    tarjan.Execute(i, &graph, &vertex_indexes);
+
+    EXPECT_EQ(2, vertex_indexes.size());
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_g));
+    EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_h));
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/topological_sort.cc b/payload_generator/topological_sort.cc
new file mode 100644
index 0000000..2a6e586
--- /dev/null
+++ b/payload_generator/topological_sort.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2010 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/topological_sort.h"
+
+#include <set>
+#include <vector>
+
+#include <base/logging.h>
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+void TopologicalSortVisit(const Graph& graph,
+                          set<Vertex::Index>* visited_nodes,
+                          vector<Vertex::Index>* nodes,
+                          Vertex::Index node) {
+  if (visited_nodes->find(node) != visited_nodes->end())
+    return;
+
+  visited_nodes->insert(node);
+  // Visit all children.
+  for (Vertex::EdgeMap::const_iterator it = graph[node].out_edges.begin();
+       it != graph[node].out_edges.end(); ++it) {
+    TopologicalSortVisit(graph, visited_nodes, nodes, it->first);
+  }
+  // Visit this node.
+  nodes->push_back(node);
+}
+}  // namespace
+
+void TopologicalSort(const Graph& graph, vector<Vertex::Index>* out) {
+  set<Vertex::Index> visited_nodes;
+
+  for (Vertex::Index i = 0; i < graph.size(); i++) {
+    TopologicalSortVisit(graph, &visited_nodes, out, i);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/topological_sort.h b/payload_generator/topological_sort.h
new file mode 100644
index 0000000..6e81d91
--- /dev/null
+++ b/payload_generator/topological_sort.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2010 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_TOPOLOGICAL_SORT_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
+
+#include <vector>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+namespace chromeos_update_engine {
+
+// Performs a topological sort on the directed graph 'graph' and stores
+// the nodes, in order visited, in 'out'.
+// For example, this graph:
+// A ---> C ----.
+//  \           v
+//   `--> B --> D
+// Might result in this in 'out':
+// out[0] = D
+// out[1] = B
+// out[2] = C
+// out[3] = A
+// Note: results are undefined if there is a cycle in the graph.
+void TopologicalSort(const Graph& graph, std::vector<Vertex::Index>* out);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_TOPOLOGICAL_SORT_H_
diff --git a/payload_generator/topological_sort_unittest.cc b/payload_generator/topological_sort_unittest.cc
new file mode 100644
index 0000000..e5e7477
--- /dev/null
+++ b/payload_generator/topological_sort_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2010 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/topological_sort.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/payload_generator/graph_types.h"
+
+using std::make_pair;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class TopologicalSortTest : public ::testing::Test {};
+
+namespace {
+// Returns true if the value is found in vect. If found, the index is stored
+// in out_index if out_index is not null.
+template<typename T>
+bool IndexOf(const vector<T>& vect,
+             const T& value,
+             typename vector<T>::size_type* out_index) {
+  for (typename vector<T>::size_type i = 0; i < vect.size(); i++) {
+    if (vect[i] == value) {
+      if (out_index) {
+        *out_index = i;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+}  // namespace
+
+TEST(TopologicalSortTest, SimpleTest) {
+  int counter = 0;
+  const Vertex::Index n_a = counter++;
+  const Vertex::Index n_b = counter++;
+  const Vertex::Index n_c = counter++;
+  const Vertex::Index n_d = counter++;
+  const Vertex::Index n_e = counter++;
+  const Vertex::Index n_f = counter++;
+  const Vertex::Index n_g = counter++;
+  const Vertex::Index n_h = counter++;
+  const Vertex::Index n_i = counter++;
+  const Vertex::Index n_j = counter++;
+  const Graph::size_type kNodeCount = counter++;
+
+  Graph graph(kNodeCount);
+
+  graph[n_i].out_edges.insert(make_pair(n_j, EdgeProperties()));
+  graph[n_i].out_edges.insert(make_pair(n_c, EdgeProperties()));
+  graph[n_i].out_edges.insert(make_pair(n_e, EdgeProperties()));
+  graph[n_i].out_edges.insert(make_pair(n_h, EdgeProperties()));
+  graph[n_c].out_edges.insert(make_pair(n_b, EdgeProperties()));
+  graph[n_b].out_edges.insert(make_pair(n_a, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_e].out_edges.insert(make_pair(n_g, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_d, EdgeProperties()));
+  graph[n_g].out_edges.insert(make_pair(n_f, EdgeProperties()));
+  graph[n_d].out_edges.insert(make_pair(n_a, EdgeProperties()));
+
+  vector<Vertex::Index> sorted;
+  TopologicalSort(graph, &sorted);
+
+  for (Vertex::Index i = 0; i < graph.size(); i++) {
+    vector<Vertex::Index>::size_type src_index = 0;
+    EXPECT_TRUE(IndexOf(sorted, i, &src_index));
+    for (Vertex::EdgeMap::const_iterator it = graph[i].out_edges.begin();
+         it != graph[i].out_edges.end(); ++it) {
+      vector<Vertex::Index>::size_type dst_index = 0;
+      EXPECT_TRUE(IndexOf(sorted, it->first, &dst_index));
+      EXPECT_LT(dst_index, src_index);
+    }
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/verity_utils.cc b/payload_generator/verity_utils.cc
new file mode 100644
index 0000000..c9198e0
--- /dev/null
+++ b/payload_generator/verity_utils.cc
@@ -0,0 +1,127 @@
+// 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/verity_utils.h"
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <chromeos/strings/string_utils.h>
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+using std::string;
+using std::vector;
+
+extern "C" {
+
+// vboot_host.h has a default VbExError() that will call exit() when a function
+// fails. We redefine that function here so it doesn't exit.
+void VbExError(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  fprintf(stderr, "ERROR: ");
+  va_end(ap);
+}
+
+}
+
+namespace {
+
+// Splits a string with zero or more arguments separated by spaces into a list
+// of strings, but respecting the double quotes. For example, the string:
+//   a="foo" b=foo c="bar baz"   "my dir"/"my file"
+// has only four arguments, since some parts are grouped together due to the
+// double quotes.
+vector<string> SplitQuotedArgs(const string arglist) {
+  vector<string> terms = chromeos::string_utils::Split(
+      arglist, " ", false, false);
+  vector<string> result;
+  string last_term;
+  size_t quotes = 0;
+  for (const string& term : terms) {
+    if (quotes % 2 == 0 && term.empty())
+      continue;
+
+    quotes += std::count(term.begin(), term.end(), '"');
+    if (last_term.empty()) {
+      last_term = term;
+    } else {
+      last_term += " " + term;
+    }
+    if (quotes % 2 == 0) {
+      result.push_back(last_term);
+      last_term.clear();
+      quotes = 0;
+    }
+  }
+  // Unterminated quoted string found.
+  if (!last_term.empty())
+    result.push_back(last_term);
+  return result;
+}
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+bool ParseVerityRootfsSize(const string& kernel_cmdline,
+                           uint64_t* rootfs_size) {
+  vector<string> kernel_args = SplitQuotedArgs(kernel_cmdline);
+
+  for (const string& arg : kernel_args) {
+    std::pair<string, string> key_value =
+        chromeos::string_utils::SplitAtFirst(arg, "=", true);
+    if (key_value.first != "dm")
+      continue;
+    string value = key_value.second;
+    if (value.size() > 1 && value.front() == '"' && value.back() == '"')
+      value = value.substr(1, value.size() - 1);
+
+    vector<string> dm_parts = SplitQuotedArgs(value);
+    // Check if this is a dm-verity device.
+    if (std::find(dm_parts.begin(), dm_parts.end(), "verity") == dm_parts.end())
+      continue;
+    for (const string& dm_part : dm_parts) {
+      key_value = chromeos::string_utils::SplitAtFirst(dm_part, "=", true);
+      if (key_value.first != "hashstart")
+        continue;
+      if (!base::StringToUint64(key_value.second, rootfs_size))
+        continue;
+      // The hashstart= value is specified in 512-byte blocks, so we need to
+      // convert that to bytes.
+      *rootfs_size *= 512;
+      return true;
+    }
+  }
+  return false;
+}
+
+bool GetVerityRootfsSize(const string& kernel_dev, uint64_t* rootfs_size) {
+  string kernel_cmdline;
+  char *config = FindKernelConfig(kernel_dev.c_str(), USE_PREAMBLE_LOAD_ADDR);
+  if (!config) {
+    LOG(WARNING) << "Error retrieving kernel command line from '"
+                 << kernel_dev << "', ignoring.";
+    return false;
+  }
+  kernel_cmdline = string(config, MAX_KERNEL_CONFIG_SIZE);
+
+  // FindKernelConfig() expects the caller to free the char*.
+  free(config);
+
+  if (!ParseVerityRootfsSize(kernel_cmdline, rootfs_size)) {
+    LOG(INFO) << "Didn't find the rootfs size in the kernel command line: "
+              << kernel_cmdline;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_generator/verity_utils.h b/payload_generator/verity_utils.h
new file mode 100644
index 0000000..3f7b297
--- /dev/null
+++ b/payload_generator/verity_utils.h
@@ -0,0 +1,19 @@
+// 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_VERITY_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+bool GetVerityRootfsSize(const std::string& kernel_dev, uint64_t* rootfs_size);
+
+bool ParseVerityRootfsSize(const std::string& kernel_cmdline,
+                           uint64_t* rootfs_size);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_VERITY_UTILS_H_
diff --git a/payload_generator/verity_utils_unittest.cc b/payload_generator/verity_utils_unittest.cc
new file mode 100644
index 0000000..d1e54e6
--- /dev/null
+++ b/payload_generator/verity_utils_unittest.cc
@@ -0,0 +1,61 @@
+// 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/verity_utils.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+// A real kernel command line found on a device.
+static const char* kVerityKernelCommandLine =
+    "console= loglevel=7 init=/sbin/init cros_secure oops=panic panic=-1 "
+    "root=/dev/dm-0 rootwait ro dm_verity.error_behavior=3 "
+    "dm_verity.max_bios=-1 dm_verity.dev_wait=1 "
+    "dm=\"1 vroot none ro 1,0 1536000 verity payload=PARTUUID=%U/PARTNROFF=1 "
+    "hashtree=PARTUUID=%U/PARTNROFF=1 hashstart=1536000 alg=sha1 "
+    "root_hexdigest=16b55bbea634fc3abf4c339da207cf050b1809d6 "
+    "salt=18a095c4e473b68558afefdf83438d482cf37894d312afce6991c8267ea233f6\" "
+    "noinitrd vt.global_cursor_default=0 kern_guid=%U ";
+
+// A real kernel command line from a parrot device, including the bootcache.
+static const char* kVerityAndBootcacheKernelCommandLine =
+    "console= loglevel=7 init=/sbin/init cros_secure oops=panic panic=-1 "
+    "root=/dev/dm-1 rootwait ro dm_verity.error_behavior=3 "
+    "dm_verity.max_bios=-1 dm_verity.dev_wait=1 "
+    "dm=\"2 vboot none ro 1,0 2545920 bootcache PARTUUID=%U/PARTNROFF=1 "
+    "2545920 d5d03fb5459b6a75f069378c1799ba313d8ea89a 512 20000 100000, vroot "
+    "none ro 1,0 2506752 verity payload=254:0 hashtree=254:0 hashstart=2506752 "
+    "alg=sha1 root_hexdigest=3deebbc697a30cc585cf85a3b4351dc772861321 "
+    "salt=6a13027cdf234c58a0b1f43e6a7428f41672cca89d5574c1f405649df65fb071\" "
+    "noinitrd vt.global_cursor_default=0 kern_guid=%U add_efi_memmap "
+    "boot=local noresume noswap i915.modeset=1 tpm_tis.force=1 "
+    "tpm_tis.interrupts=0 nmi_watchdog=panic,lapic "
+    "iTCO_vendor_support.vendorsupport=3";
+
+TEST(VerityUtilsTest, ParseVerityRootfsSizeWithInvalidValues) {
+  uint64_t rootfs_size = 0;
+  EXPECT_FALSE(ParseVerityRootfsSize("", &rootfs_size));
+
+  // Not a verity dm device.
+  EXPECT_FALSE(ParseVerityRootfsSize(
+      "dm=\"1 vroot none ro 1,0 1234 something\"", &rootfs_size));
+  EXPECT_FALSE(ParseVerityRootfsSize(
+      "ro verity hashattr=1234", &rootfs_size));
+
+  // The verity doesn't have the hashstart= attribute.
+  EXPECT_FALSE(ParseVerityRootfsSize(
+      "dm=\"1 vroot none ro 1,0 1234 verity payload=fake\"", &rootfs_size));
+}
+
+TEST(VerityUtilsTest, ParseVerityRootfsSizeWithValidValues) {
+  uint64_t rootfs_size = 0;
+  EXPECT_TRUE(ParseVerityRootfsSize(kVerityKernelCommandLine, &rootfs_size));
+  EXPECT_EQ(1536000 * 512, rootfs_size);
+  EXPECT_TRUE(ParseVerityRootfsSize(kVerityAndBootcacheKernelCommandLine,
+                                    &rootfs_size));
+  EXPECT_EQ(2506752 * 512, rootfs_size);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_state.cc b/payload_state.cc
new file mode 100644
index 0000000..ed7b775
--- /dev/null
+++ b/payload_state.cc
@@ -0,0 +1,1568 @@
+// Copyright (c) 2012 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_state.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/format_macros.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/clock.h"
+#include "update_engine/constants.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs.h"
+#include "update_engine/real_dbus_wrapper.h"
+#include "update_engine/system_state.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const TimeDelta PayloadState::kDurationSlack = TimeDelta::FromSeconds(600);
+
+// We want to upperbound backoffs to 16 days
+static const int kMaxBackoffDays = 16;
+
+// We want to randomize retry attempts after the backoff by +/- 6 hours.
+static const uint32_t kMaxBackoffFuzzMinutes = 12 * 60;
+
+PayloadState::PayloadState()
+    : prefs_(nullptr),
+      using_p2p_for_downloading_(false),
+      p2p_num_attempts_(0),
+      payload_attempt_number_(0),
+      full_payload_attempt_number_(0),
+      url_index_(0),
+      url_failure_count_(0),
+      url_switch_count_(0),
+      attempt_num_bytes_downloaded_(0),
+      attempt_connection_type_(metrics::ConnectionType::kUnknown),
+      attempt_type_(AttemptType::kUpdate) {
+  for (int i = 0; i <= kNumDownloadSources; i++)
+    total_bytes_downloaded_[i] = current_bytes_downloaded_[i] = 0;
+}
+
+bool PayloadState::Initialize(SystemState* system_state) {
+  system_state_ = system_state;
+  prefs_ = system_state_->prefs();
+  powerwash_safe_prefs_ = system_state_->powerwash_safe_prefs();
+  LoadResponseSignature();
+  LoadPayloadAttemptNumber();
+  LoadFullPayloadAttemptNumber();
+  LoadUrlIndex();
+  LoadUrlFailureCount();
+  LoadUrlSwitchCount();
+  LoadBackoffExpiryTime();
+  LoadUpdateTimestampStart();
+  // The LoadUpdateDurationUptime() method relies on LoadUpdateTimestampStart()
+  // being called before it. Don't reorder.
+  LoadUpdateDurationUptime();
+  for (int i = 0; i < kNumDownloadSources; i++) {
+    DownloadSource source = static_cast<DownloadSource>(i);
+    LoadCurrentBytesDownloaded(source);
+    LoadTotalBytesDownloaded(source);
+  }
+  LoadNumReboots();
+  LoadNumResponsesSeen();
+  LoadRollbackVersion();
+  LoadP2PFirstAttemptTimestamp();
+  LoadP2PNumAttempts();
+  return true;
+}
+
+void PayloadState::SetResponse(const OmahaResponse& omaha_response) {
+  // Always store the latest response.
+  response_ = omaha_response;
+
+  // Compute the candidate URLs first as they are used to calculate the
+  // response signature so that a change in enterprise policy for
+  // HTTP downloads being enabled or not could be honored as soon as the
+  // next update check happens.
+  ComputeCandidateUrls();
+
+  // Check if the "signature" of this response (i.e. the fields we care about)
+  // has changed.
+  string new_response_signature = CalculateResponseSignature();
+  bool has_response_changed = (response_signature_ != new_response_signature);
+
+  // If the response has changed, we should persist the new signature and
+  // clear away all the existing state.
+  if (has_response_changed) {
+    LOG(INFO) << "Resetting all persisted state as this is a new response";
+    SetNumResponsesSeen(num_responses_seen_ + 1);
+    SetResponseSignature(new_response_signature);
+    ResetPersistedState();
+    ReportUpdatesAbandonedEventCountMetric();
+    return;
+  }
+
+  // This is the earliest point at which we can validate whether the URL index
+  // we loaded from the persisted state is a valid value. If the response
+  // hasn't changed but the URL index is invalid, it's indicative of some
+  // tampering of the persisted state.
+  if (static_cast<uint32_t>(url_index_) >= candidate_urls_.size()) {
+    LOG(INFO) << "Resetting all payload state as the url index seems to have "
+                 "been tampered with";
+    ResetPersistedState();
+    return;
+  }
+
+  // Update the current download source which depends on the latest value of
+  // the response.
+  UpdateCurrentDownloadSource();
+}
+
+void PayloadState::SetUsingP2PForDownloading(bool value) {
+  using_p2p_for_downloading_ = value;
+  // Update the current download source which depends on whether we are
+  // using p2p or not.
+  UpdateCurrentDownloadSource();
+}
+
+void PayloadState::DownloadComplete() {
+  LOG(INFO) << "Payload downloaded successfully";
+  IncrementPayloadAttemptNumber();
+  IncrementFullPayloadAttemptNumber();
+}
+
+void PayloadState::DownloadProgress(size_t count) {
+  if (count == 0)
+    return;
+
+  CalculateUpdateDurationUptime();
+  UpdateBytesDownloaded(count);
+
+  // We've received non-zero bytes from a recent download operation.  Since our
+  // URL failure count is meant to penalize a URL only for consecutive
+  // failures, downloading bytes successfully means we should reset the failure
+  // count (as we know at least that the URL is working). In future, we can
+  // design this to be more sophisticated to check for more intelligent failure
+  // patterns, but right now, even 1 byte downloaded will mark the URL to be
+  // good unless it hits 10 (or configured number of) consecutive failures
+  // again.
+
+  if (GetUrlFailureCount() == 0)
+    return;
+
+  LOG(INFO) << "Resetting failure count of Url" << GetUrlIndex()
+            << " to 0 as we received " << count << " bytes successfully";
+  SetUrlFailureCount(0);
+}
+
+void PayloadState::AttemptStarted(AttemptType attempt_type) {
+  // Flush previous state from abnormal attempt failure, if any.
+  ReportAndClearPersistedAttemptMetrics();
+
+  attempt_type_ = attempt_type;
+
+  ClockInterface *clock = system_state_->clock();
+  attempt_start_time_boot_ = clock->GetBootTime();
+  attempt_start_time_monotonic_ = clock->GetMonotonicTime();
+  attempt_num_bytes_downloaded_ = 0;
+
+  metrics::ConnectionType type;
+  NetworkConnectionType network_connection_type;
+  NetworkTethering tethering;
+  RealDBusWrapper dbus_iface;
+  ConnectionManager* connection_manager = system_state_->connection_manager();
+  if (!connection_manager->GetConnectionProperties(&dbus_iface,
+                                                   &network_connection_type,
+                                                   &tethering)) {
+    LOG(ERROR) << "Failed to determine connection type.";
+    type = metrics::ConnectionType::kUnknown;
+  } else {
+    type = utils::GetConnectionType(network_connection_type, tethering);
+  }
+  attempt_connection_type_ = type;
+
+  if (attempt_type == AttemptType::kUpdate)
+    PersistAttemptMetrics();
+}
+
+void PayloadState::UpdateResumed() {
+  LOG(INFO) << "Resuming an update that was previously started.";
+  UpdateNumReboots();
+  AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateRestarted() {
+  LOG(INFO) << "Starting a new update";
+  ResetDownloadSourcesOnNewUpdate();
+  SetNumReboots(0);
+  AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateSucceeded() {
+  // Send the relevant metrics that are tracked in this class to UMA.
+  CalculateUpdateDurationUptime();
+  SetUpdateTimestampEnd(system_state_->clock()->GetWallclockTime());
+
+  switch (attempt_type_) {
+    case AttemptType::kUpdate:
+      CollectAndReportAttemptMetrics(ErrorCode::kSuccess);
+      CollectAndReportSuccessfulUpdateMetrics();
+      ClearPersistedAttemptMetrics();
+      break;
+
+    case AttemptType::kRollback:
+      metrics::ReportRollbackMetrics(system_state_,
+                                     metrics::RollbackResult::kSuccess);
+      break;
+  }
+
+  // Reset the number of responses seen since it counts from the last
+  // successful update, e.g. now.
+  SetNumResponsesSeen(0);
+
+  CreateSystemUpdatedMarkerFile();
+}
+
+void PayloadState::UpdateFailed(ErrorCode error) {
+  ErrorCode base_error = utils::GetBaseErrorCode(error);
+  LOG(INFO) << "Updating payload state for error code: " << base_error
+            << " (" << utils::CodeToString(base_error) << ")";
+
+  if (candidate_urls_.size() == 0) {
+    // This means we got this error even before we got a valid Omaha response
+    // or don't have any valid candidates in the Omaha response.
+    // So we should not advance the url_index_ in such cases.
+    LOG(INFO) << "Ignoring failures until we get a valid Omaha response.";
+    return;
+  }
+
+  switch (attempt_type_) {
+    case AttemptType::kUpdate:
+      CollectAndReportAttemptMetrics(base_error);
+      ClearPersistedAttemptMetrics();
+      break;
+
+    case AttemptType::kRollback:
+      metrics::ReportRollbackMetrics(system_state_,
+                                     metrics::RollbackResult::kFailed);
+      break;
+  }
+
+  switch (base_error) {
+    // Errors which are good indicators of a problem with a particular URL or
+    // the protocol used in the URL or entities in the communication channel
+    // (e.g. proxies). We should try the next available URL in the next update
+    // check to quickly recover from these errors.
+    case ErrorCode::kPayloadHashMismatchError:
+    case ErrorCode::kPayloadSizeMismatchError:
+    case ErrorCode::kDownloadPayloadVerificationError:
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+    case ErrorCode::kDownloadInvalidMetadataMagicString:
+    case ErrorCode::kDownloadSignatureMissingInManifest:
+    case ErrorCode::kDownloadManifestParseError:
+    case ErrorCode::kDownloadMetadataSignatureError:
+    case ErrorCode::kDownloadMetadataSignatureVerificationError:
+    case ErrorCode::kDownloadMetadataSignatureMismatch:
+    case ErrorCode::kDownloadOperationHashVerificationError:
+    case ErrorCode::kDownloadOperationExecutionError:
+    case ErrorCode::kDownloadOperationHashMismatch:
+    case ErrorCode::kDownloadInvalidMetadataSize:
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+    case ErrorCode::kDownloadOperationHashMissingError:
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+    case ErrorCode::kPayloadMismatchedType:
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+      IncrementUrlIndex();
+      break;
+
+    // Errors which seem to be just transient network/communication related
+    // failures and do not indicate any inherent problem with the URL itself.
+    // So, we should keep the current URL but just increment the
+    // failure count to give it more chances. This way, while we maximize our
+    // chances of downloading from the URLs that appear earlier in the response
+    // (because download from a local server URL that appears earlier in a
+    // response is preferable than downloading from the next URL which could be
+    // a internet URL and thus could be more expensive).
+
+    case ErrorCode::kError:
+    case ErrorCode::kDownloadTransferError:
+    case ErrorCode::kDownloadWriteError:
+    case ErrorCode::kDownloadStateInitializationError:
+    case ErrorCode::kOmahaErrorInHTTPResponse:  // Aggregate for HTTP errors.
+      IncrementFailureCount();
+      break;
+
+    // Errors which are not specific to a URL and hence shouldn't result in
+    // the URL being penalized. This can happen in two cases:
+    // 1. We haven't started downloading anything: These errors don't cost us
+    // anything in terms of actual payload bytes, so we should just do the
+    // regular retries at the next update check.
+    // 2. We have successfully downloaded the payload: In this case, the
+    // payload attempt number would have been incremented and would take care
+    // of the backoff at the next update check.
+    // In either case, there's no need to update URL index or failure count.
+    case ErrorCode::kOmahaRequestError:
+    case ErrorCode::kOmahaResponseHandlerError:
+    case ErrorCode::kPostinstallRunnerError:
+    case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kInstallDeviceOpenError:
+    case ErrorCode::kKernelDeviceOpenError:
+    case ErrorCode::kDownloadNewPartitionInfoError:
+    case ErrorCode::kNewRootfsVerificationError:
+    case ErrorCode::kNewKernelVerificationError:
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+    case ErrorCode::kOmahaRequestEmptyResponseError:
+    case ErrorCode::kOmahaRequestXMLParseError:
+    case ErrorCode::kOmahaResponseInvalid:
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+    case ErrorCode::kPostinstallPowerwashError:
+    case ErrorCode::kUpdateCanceledByChannelChange:
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kFilesystemVerifierError:
+      LOG(INFO) << "Not incrementing URL index or failure count for this error";
+      break;
+
+    case ErrorCode::kSuccess:                            // success code
+    case ErrorCode::kUmaReportedMax:                     // not an error code
+    case ErrorCode::kOmahaRequestHTTPResponseBase:       // aggregated already
+    case ErrorCode::kDevModeFlag:                       // not an error code
+    case ErrorCode::kResumedFlag:                        // not an error code
+    case ErrorCode::kTestImageFlag:                      // not an error code
+    case ErrorCode::kTestOmahaUrlFlag:                   // not an error code
+    case ErrorCode::kSpecialFlags:                       // not an error code
+      // These shouldn't happen. Enumerating these  explicitly here so that we
+      // can let the compiler warn about new error codes that are added to
+      // action_processor.h but not added here.
+      LOG(WARNING) << "Unexpected error code for UpdateFailed";
+      break;
+
+    // Note: Not adding a default here so as to let the compiler warn us of
+    // any new enums that were added in the .h but not listed in this switch.
+  }
+}
+
+bool PayloadState::ShouldBackoffDownload() {
+  if (response_.disable_payload_backoff) {
+    LOG(INFO) << "Payload backoff logic is disabled. "
+                 "Can proceed with the download";
+    return false;
+  }
+  if (GetUsingP2PForDownloading() && !GetP2PUrl().empty()) {
+    LOG(INFO) << "Payload backoff logic is disabled because download "
+              << "will happen from local peer (via p2p).";
+    return false;
+  }
+  if (system_state_->request_params()->interactive()) {
+    LOG(INFO) << "Payload backoff disabled for interactive update checks.";
+    return false;
+  }
+  if (response_.is_delta_payload) {
+    // If delta payloads fail, we want to fallback quickly to full payloads as
+    // they are more likely to succeed. Exponential backoffs would greatly
+    // slow down the fallback to full payloads.  So we don't backoff for delta
+    // payloads.
+    LOG(INFO) << "No backoffs for delta payloads. "
+              << "Can proceed with the download";
+    return false;
+  }
+
+  if (!system_state_->hardware()->IsOfficialBuild()) {
+    // Backoffs are needed only for official builds. We do not want any delays
+    // or update failures due to backoffs during testing or development.
+    LOG(INFO) << "No backoffs for test/dev images. "
+              << "Can proceed with the download";
+    return false;
+  }
+
+  if (backoff_expiry_time_.is_null()) {
+    LOG(INFO) << "No backoff expiry time has been set. "
+              << "Can proceed with the download";
+    return false;
+  }
+
+  if (backoff_expiry_time_ < Time::Now()) {
+    LOG(INFO) << "The backoff expiry time ("
+              << utils::ToString(backoff_expiry_time_)
+              << ") has elapsed. Can proceed with the download";
+    return false;
+  }
+
+  LOG(INFO) << "Cannot proceed with downloads as we need to backoff until "
+            << utils::ToString(backoff_expiry_time_);
+  return true;
+}
+
+void PayloadState::Rollback() {
+  SetRollbackVersion(system_state_->request_params()->app_version());
+  AttemptStarted(AttemptType::kRollback);
+}
+
+void PayloadState::IncrementPayloadAttemptNumber() {
+  // Update the payload attempt number for both payload types: full and delta.
+  SetPayloadAttemptNumber(GetPayloadAttemptNumber() + 1);
+
+  // Report the metric every time the value is incremented.
+  string metric = "Installer.PayloadAttemptNumber";
+  int value = GetPayloadAttemptNumber();
+
+  LOG(INFO) << "Uploading " << value << " (count) for metric " <<  metric;
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       value,
+       1,    // min value
+       50,   // max value
+       kNumDefaultUmaBuckets);
+}
+
+void PayloadState::IncrementFullPayloadAttemptNumber() {
+  // Update the payload attempt number for full payloads and the backoff time.
+  if (response_.is_delta_payload) {
+    LOG(INFO) << "Not incrementing payload attempt number for delta payloads";
+    return;
+  }
+
+  LOG(INFO) << "Incrementing the full payload attempt number";
+  SetFullPayloadAttemptNumber(GetFullPayloadAttemptNumber() + 1);
+  UpdateBackoffExpiryTime();
+
+  // Report the metric every time the value is incremented.
+  string metric = "Installer.FullPayloadAttemptNumber";
+  int value = GetFullPayloadAttemptNumber();
+
+  LOG(INFO) << "Uploading " << value << " (count) for metric " <<  metric;
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       value,
+       1,    // min value
+       50,   // max value
+       kNumDefaultUmaBuckets);
+}
+
+void PayloadState::IncrementUrlIndex() {
+  uint32_t next_url_index = GetUrlIndex() + 1;
+  if (next_url_index < candidate_urls_.size()) {
+    LOG(INFO) << "Incrementing the URL index for next attempt";
+    SetUrlIndex(next_url_index);
+  } else {
+    LOG(INFO) << "Resetting the current URL index (" << GetUrlIndex() << ") to "
+              << "0 as we only have " << candidate_urls_.size()
+              << " candidate URL(s)";
+    SetUrlIndex(0);
+    IncrementPayloadAttemptNumber();
+    IncrementFullPayloadAttemptNumber();
+  }
+
+  // If we have multiple URLs, record that we just switched to another one
+  if (candidate_urls_.size() > 1)
+    SetUrlSwitchCount(url_switch_count_ + 1);
+
+  // Whenever we update the URL index, we should also clear the URL failure
+  // count so we can start over fresh for the new URL.
+  SetUrlFailureCount(0);
+}
+
+void PayloadState::IncrementFailureCount() {
+  uint32_t next_url_failure_count = GetUrlFailureCount() + 1;
+  if (next_url_failure_count < response_.max_failure_count_per_url) {
+    LOG(INFO) << "Incrementing the URL failure count";
+    SetUrlFailureCount(next_url_failure_count);
+  } else {
+    LOG(INFO) << "Reached max number of failures for Url" << GetUrlIndex()
+              << ". Trying next available URL";
+    IncrementUrlIndex();
+  }
+}
+
+void PayloadState::UpdateBackoffExpiryTime() {
+  if (response_.disable_payload_backoff) {
+    LOG(INFO) << "Resetting backoff expiry time as payload backoff is disabled";
+    SetBackoffExpiryTime(Time());
+    return;
+  }
+
+  if (GetFullPayloadAttemptNumber() == 0) {
+    SetBackoffExpiryTime(Time());
+    return;
+  }
+
+  // Since we're doing left-shift below, make sure we don't shift more
+  // than this. E.g. if int is 4-bytes, don't left-shift more than 30 bits,
+  // since we don't expect value of kMaxBackoffDays to be more than 100 anyway.
+  int num_days = 1;  // the value to be shifted.
+  const int kMaxShifts = (sizeof(num_days) * 8) - 2;
+
+  // Normal backoff days is 2 raised to (payload_attempt_number - 1).
+  // E.g. if payload_attempt_number is over 30, limit power to 30.
+  int power = min(GetFullPayloadAttemptNumber() - 1, kMaxShifts);
+
+  // The number of days is the minimum of 2 raised to (payload_attempt_number
+  // - 1) or kMaxBackoffDays.
+  num_days = min(num_days << power, kMaxBackoffDays);
+
+  // We don't want all retries to happen exactly at the same time when
+  // retrying after backoff. So add some random minutes to fuzz.
+  int fuzz_minutes = utils::FuzzInt(0, kMaxBackoffFuzzMinutes);
+  TimeDelta next_backoff_interval = TimeDelta::FromDays(num_days) +
+                                    TimeDelta::FromMinutes(fuzz_minutes);
+  LOG(INFO) << "Incrementing the backoff expiry time by "
+            << utils::FormatTimeDelta(next_backoff_interval);
+  SetBackoffExpiryTime(Time::Now() + next_backoff_interval);
+}
+
+void PayloadState::UpdateCurrentDownloadSource() {
+  current_download_source_ = kNumDownloadSources;
+
+  if (using_p2p_for_downloading_) {
+    current_download_source_ = kDownloadSourceHttpPeer;
+  } else if (GetUrlIndex() < candidate_urls_.size())  {
+    string current_url = candidate_urls_[GetUrlIndex()];
+    if (base::StartsWithASCII(current_url, "https://", false))
+      current_download_source_ = kDownloadSourceHttpsServer;
+    else if (base::StartsWithASCII(current_url, "http://", false))
+      current_download_source_ = kDownloadSourceHttpServer;
+  }
+
+  LOG(INFO) << "Current download source: "
+            << utils::ToString(current_download_source_);
+}
+
+void PayloadState::UpdateBytesDownloaded(size_t count) {
+  SetCurrentBytesDownloaded(
+      current_download_source_,
+      GetCurrentBytesDownloaded(current_download_source_) + count,
+      false);
+  SetTotalBytesDownloaded(
+      current_download_source_,
+      GetTotalBytesDownloaded(current_download_source_) + count,
+      false);
+
+  attempt_num_bytes_downloaded_ += count;
+}
+
+PayloadType PayloadState::CalculatePayloadType() {
+  PayloadType payload_type;
+  OmahaRequestParams* params = system_state_->request_params();
+  if (response_.is_delta_payload) {
+    payload_type = kPayloadTypeDelta;
+  } else if (params->delta_okay()) {
+    payload_type = kPayloadTypeFull;
+  } else {  // Full payload, delta was not allowed by request.
+    payload_type = kPayloadTypeForcedFull;
+  }
+  return payload_type;
+}
+
+// TODO(zeuthen): Currently we don't report the UpdateEngine.Attempt.*
+// metrics if the attempt ends abnormally, e.g. if the update_engine
+// process crashes or the device is rebooted. See
+// http://crbug.com/357676
+void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) {
+  int attempt_number = GetPayloadAttemptNumber();
+
+  PayloadType payload_type = CalculatePayloadType();
+
+  int64_t payload_size = response_.size;
+
+  int64_t payload_bytes_downloaded = attempt_num_bytes_downloaded_;
+
+  ClockInterface *clock = system_state_->clock();
+  TimeDelta duration = clock->GetBootTime() - attempt_start_time_boot_;
+  TimeDelta duration_uptime = clock->GetMonotonicTime() -
+      attempt_start_time_monotonic_;
+
+  int64_t payload_download_speed_bps = 0;
+  int64_t usec = duration_uptime.InMicroseconds();
+  if (usec > 0) {
+    double sec = static_cast<double>(usec) / Time::kMicrosecondsPerSecond;
+    double bps = static_cast<double>(payload_bytes_downloaded) / sec;
+    payload_download_speed_bps = static_cast<int64_t>(bps);
+  }
+
+  DownloadSource download_source = current_download_source_;
+
+  metrics::DownloadErrorCode payload_download_error_code =
+    metrics::DownloadErrorCode::kUnset;
+  ErrorCode internal_error_code = ErrorCode::kSuccess;
+  metrics::AttemptResult attempt_result = utils::GetAttemptResult(code);
+
+  // Add additional detail to AttemptResult
+  switch (attempt_result) {
+    case metrics::AttemptResult::kPayloadDownloadError:
+      payload_download_error_code = utils::GetDownloadErrorCode(code);
+      break;
+
+    case metrics::AttemptResult::kInternalError:
+      internal_error_code = code;
+      break;
+
+    // Explicit fall-through for cases where we do not have additional
+    // detail. We avoid the default keyword to force people adding new
+    // AttemptResult values to visit this code and examine whether
+    // additional detail is needed.
+    case metrics::AttemptResult::kUpdateSucceeded:
+    case metrics::AttemptResult::kMetadataMalformed:
+    case metrics::AttemptResult::kOperationMalformed:
+    case metrics::AttemptResult::kOperationExecutionError:
+    case metrics::AttemptResult::kMetadataVerificationFailed:
+    case metrics::AttemptResult::kPayloadVerificationFailed:
+    case metrics::AttemptResult::kVerificationFailed:
+    case metrics::AttemptResult::kPostInstallFailed:
+    case metrics::AttemptResult::kAbnormalTermination:
+    case metrics::AttemptResult::kNumConstants:
+    case metrics::AttemptResult::kUnset:
+      break;
+  }
+
+  metrics::ReportUpdateAttemptMetrics(system_state_,
+                                      attempt_number,
+                                      payload_type,
+                                      duration,
+                                      duration_uptime,
+                                      payload_size,
+                                      payload_bytes_downloaded,
+                                      payload_download_speed_bps,
+                                      download_source,
+                                      attempt_result,
+                                      internal_error_code,
+                                      payload_download_error_code,
+                                      attempt_connection_type_);
+}
+
+void PayloadState::PersistAttemptMetrics() {
+  // TODO(zeuthen): For now we only persist whether an attempt was in
+  // progress and not values/metrics related to the attempt. This
+  // means that when this happens, of all the UpdateEngine.Attempt.*
+  // metrics, only UpdateEngine.Attempt.Result is reported (with the
+  // value |kAbnormalTermination|). In the future we might want to
+  // persist more data so we can report other metrics in the
+  // UpdateEngine.Attempt.* namespace when this happens.
+  prefs_->SetBoolean(kPrefsAttemptInProgress, true);
+}
+
+void PayloadState::ClearPersistedAttemptMetrics() {
+  prefs_->Delete(kPrefsAttemptInProgress);
+}
+
+void PayloadState::ReportAndClearPersistedAttemptMetrics() {
+  bool attempt_in_progress = false;
+  if (!prefs_->GetBoolean(kPrefsAttemptInProgress, &attempt_in_progress))
+    return;
+  if (!attempt_in_progress)
+    return;
+
+  metrics::ReportAbnormallyTerminatedUpdateAttemptMetrics(system_state_);
+
+  ClearPersistedAttemptMetrics();
+}
+
+void PayloadState::CollectAndReportSuccessfulUpdateMetrics() {
+  string metric;
+
+  // Report metrics collected from all known download sources to UMA.
+  int64_t successful_bytes_by_source[kNumDownloadSources];
+  int64_t total_bytes_by_source[kNumDownloadSources];
+  int64_t successful_bytes = 0;
+  int64_t total_bytes = 0;
+  int64_t successful_mbs = 0;
+  int64_t total_mbs = 0;
+
+  for (int i = 0; i < kNumDownloadSources; i++) {
+    DownloadSource source = static_cast<DownloadSource>(i);
+    int64_t bytes;
+
+    // Only consider this download source (and send byte counts) as
+    // having been used if we downloaded a non-trivial amount of bytes
+    // (e.g. at least 1 MiB) that contributed to the final success of
+    // the update. Otherwise we're going to end up with a lot of
+    // zero-byte events in the histogram.
+
+    bytes = GetCurrentBytesDownloaded(source);
+    successful_bytes_by_source[i] = bytes;
+    successful_bytes += bytes;
+    successful_mbs += bytes / kNumBytesInOneMiB;
+    SetCurrentBytesDownloaded(source, 0, true);
+
+    bytes = GetTotalBytesDownloaded(source);
+    total_bytes_by_source[i] = bytes;
+    total_bytes += bytes;
+    total_mbs += bytes / kNumBytesInOneMiB;
+    SetTotalBytesDownloaded(source, 0, true);
+  }
+
+  int download_overhead_percentage = 0;
+  if (successful_bytes > 0) {
+    download_overhead_percentage = (total_bytes - successful_bytes) * 100ULL /
+                                   successful_bytes;
+  }
+
+  int url_switch_count = static_cast<int>(url_switch_count_);
+
+  int reboot_count = GetNumReboots();
+
+  SetNumReboots(0);
+
+  TimeDelta duration = GetUpdateDuration();
+  TimeDelta duration_uptime = GetUpdateDurationUptime();
+
+  prefs_->Delete(kPrefsUpdateTimestampStart);
+  prefs_->Delete(kPrefsUpdateDurationUptime);
+
+  PayloadType payload_type = CalculatePayloadType();
+
+  int64_t payload_size = response_.size;
+
+  int attempt_count = GetPayloadAttemptNumber();
+
+  int updates_abandoned_count = num_responses_seen_ - 1;
+
+  metrics::ReportSuccessfulUpdateMetrics(system_state_,
+                                         attempt_count,
+                                         updates_abandoned_count,
+                                         payload_type,
+                                         payload_size,
+                                         total_bytes_by_source,
+                                         download_overhead_percentage,
+                                         duration,
+                                         reboot_count,
+                                         url_switch_count);
+
+  // TODO(zeuthen): This is the old metric reporting code which is
+  // slated for removal soon. See http://crbug.com/355745 for details.
+
+  // The old metrics code is using MiB's instead of bytes to calculate
+  // the overhead which due to rounding makes the numbers slightly
+  // different.
+  download_overhead_percentage = 0;
+  if (successful_mbs > 0) {
+    download_overhead_percentage = (total_mbs - successful_mbs) * 100ULL /
+                                   successful_mbs;
+  }
+
+  int download_sources_used = 0;
+  for (int i = 0; i < kNumDownloadSources; i++) {
+    DownloadSource source = static_cast<DownloadSource>(i);
+    const int kMaxMiBs = 10240;  // Anything above 10GB goes in the last bucket.
+    int64_t mbs;
+
+    // Only consider this download source (and send byte counts) as
+    // having been used if we downloaded a non-trivial amount of bytes
+    // (e.g. at least 1 MiB) that contributed to the final success of
+    // the update. Otherwise we're going to end up with a lot of
+    // zero-byte events in the histogram.
+
+    mbs = successful_bytes_by_source[i] / kNumBytesInOneMiB;
+    if (mbs > 0) {
+      metric = "Installer.SuccessfulMBsDownloadedFrom" +
+          utils::ToString(source);
+      LOG(INFO) << "Uploading " << mbs << " (MBs) for metric " << metric;
+      system_state_->metrics_lib()->SendToUMA(metric,
+                                              mbs,
+                                              0,  // min
+                                              kMaxMiBs,
+                                              kNumDefaultUmaBuckets);
+    }
+
+    mbs = total_bytes_by_source[i] / kNumBytesInOneMiB;
+    if (mbs > 0) {
+      metric = "Installer.TotalMBsDownloadedFrom" + utils::ToString(source);
+      LOG(INFO) << "Uploading " << mbs << " (MBs) for metric " << metric;
+      system_state_->metrics_lib()->SendToUMA(metric,
+                                              mbs,
+                                              0,  // min
+                                              kMaxMiBs,
+                                              kNumDefaultUmaBuckets);
+      download_sources_used |= (1 << i);
+    }
+  }
+
+  metric = "Installer.DownloadSourcesUsed";
+  LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
+            << " (bit flags) for metric " << metric;
+  int num_buckets = min(1 << kNumDownloadSources, kNumDefaultUmaBuckets);
+  system_state_->metrics_lib()->SendToUMA(metric,
+                                          download_sources_used,
+                                          0,  // min
+                                          1 << kNumDownloadSources,
+                                          num_buckets);
+
+  metric = "Installer.DownloadOverheadPercentage";
+  LOG(INFO) << "Uploading " << download_overhead_percentage
+            << "% for metric " << metric;
+  system_state_->metrics_lib()->SendToUMA(metric,
+                                          download_overhead_percentage,
+                                          0,     // min: 0% overhead
+                                          1000,  // max: 1000% overhead
+                                          kNumDefaultUmaBuckets);
+
+  metric = "Installer.UpdateURLSwitches";
+  LOG(INFO) << "Uploading " << url_switch_count
+            << " (count) for metric " << metric;
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       url_switch_count,
+       0,    // min value
+       100,  // max value
+       kNumDefaultUmaBuckets);
+
+  metric = "Installer.UpdateNumReboots";
+  LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
+            <<  metric;
+  system_state_->metrics_lib()->SendToUMA(
+      metric,
+      reboot_count,  // sample
+      0,    // min = 0.
+      50,   // max
+      25);  // buckets
+
+  metric = "Installer.UpdateDurationMinutes";
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       static_cast<int>(duration.InMinutes()),
+       1,             // min: 1 minute
+       365*24*60,     // max: 1 year (approx)
+       kNumDefaultUmaBuckets);
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
+            << " for metric " <<  metric;
+
+  metric = "Installer.UpdateDurationUptimeMinutes";
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       static_cast<int>(duration_uptime.InMinutes()),
+       1,             // min: 1 minute
+       30*24*60,      // max: 1 month (approx)
+       kNumDefaultUmaBuckets);
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
+            << " for metric " <<  metric;
+
+  metric = "Installer.PayloadFormat";
+  system_state_->metrics_lib()->SendEnumToUMA(
+      metric,
+      payload_type,
+      kNumPayloadTypes);
+  LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+            << " for metric " <<  metric;
+
+  metric = "Installer.AttemptsCount.Total";
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       attempt_count,
+       1,      // min
+       50,     // max
+       kNumDefaultUmaBuckets);
+  LOG(INFO) << "Uploading " << attempt_count
+            << " for metric " <<  metric;
+
+  metric = "Installer.UpdatesAbandonedCount";
+  LOG(INFO) << "Uploading " << updates_abandoned_count
+            << " (count) for metric " <<  metric;
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       updates_abandoned_count,
+       0,    // min value
+       100,  // max value
+       kNumDefaultUmaBuckets);
+}
+
+void PayloadState::UpdateNumReboots() {
+  // We only update the reboot count when the system has been detected to have
+  // been rebooted.
+  if (!system_state_->system_rebooted()) {
+    return;
+  }
+
+  SetNumReboots(GetNumReboots() + 1);
+}
+
+void PayloadState::SetNumReboots(uint32_t num_reboots) {
+  CHECK(prefs_);
+  num_reboots_ = num_reboots;
+  prefs_->SetInt64(kPrefsNumReboots, num_reboots);
+  LOG(INFO) << "Number of Reboots during current update attempt = "
+            << num_reboots_;
+}
+
+void PayloadState::ResetPersistedState() {
+  SetPayloadAttemptNumber(0);
+  SetFullPayloadAttemptNumber(0);
+  SetUrlIndex(0);
+  SetUrlFailureCount(0);
+  SetUrlSwitchCount(0);
+  UpdateBackoffExpiryTime();  // This will reset the backoff expiry time.
+  SetUpdateTimestampStart(system_state_->clock()->GetWallclockTime());
+  SetUpdateTimestampEnd(Time());  // Set to null time
+  SetUpdateDurationUptime(TimeDelta::FromSeconds(0));
+  ResetDownloadSourcesOnNewUpdate();
+  ResetRollbackVersion();
+  SetP2PNumAttempts(0);
+  SetP2PFirstAttemptTimestamp(Time());  // Set to null time
+  SetScatteringWaitPeriod(TimeDelta());
+}
+
+void PayloadState::ResetRollbackVersion() {
+  CHECK(powerwash_safe_prefs_);
+  rollback_version_ = "";
+  powerwash_safe_prefs_->Delete(kPrefsRollbackVersion);
+}
+
+void PayloadState::ResetDownloadSourcesOnNewUpdate() {
+  for (int i = 0; i < kNumDownloadSources; i++) {
+    DownloadSource source = static_cast<DownloadSource>(i);
+    SetCurrentBytesDownloaded(source, 0, true);
+    // Note: Not resetting the TotalBytesDownloaded as we want that metric
+    // to count the bytes downloaded across various update attempts until
+    // we have successfully applied the update.
+  }
+}
+
+int64_t PayloadState::GetPersistedValue(const string& key) {
+  CHECK(prefs_);
+  if (!prefs_->Exists(key))
+    return 0;
+
+  int64_t stored_value;
+  if (!prefs_->GetInt64(key, &stored_value))
+    return 0;
+
+  if (stored_value < 0) {
+    LOG(ERROR) << key << ": Invalid value (" << stored_value
+               << ") in persisted state. Defaulting to 0";
+    return 0;
+  }
+
+  return stored_value;
+}
+
+string PayloadState::CalculateResponseSignature() {
+  string response_sign = base::StringPrintf(
+      "NumURLs = %d\n", static_cast<int>(candidate_urls_.size()));
+
+  for (size_t i = 0; i < candidate_urls_.size(); i++)
+    response_sign += base::StringPrintf("Candidate Url%d = %s\n",
+                                        static_cast<int>(i),
+                                        candidate_urls_[i].c_str());
+
+  response_sign += base::StringPrintf(
+      "Payload Size = %ju\n"
+      "Payload Sha256 Hash = %s\n"
+      "Metadata Size = %ju\n"
+      "Metadata Signature = %s\n"
+      "Is Delta Payload = %d\n"
+      "Max Failure Count Per Url = %d\n"
+      "Disable Payload Backoff = %d\n",
+      static_cast<uintmax_t>(response_.size),
+      response_.hash.c_str(),
+      static_cast<uintmax_t>(response_.metadata_size),
+      response_.metadata_signature.c_str(),
+      response_.is_delta_payload,
+      response_.max_failure_count_per_url,
+      response_.disable_payload_backoff);
+  return response_sign;
+}
+
+void PayloadState::LoadResponseSignature() {
+  CHECK(prefs_);
+  string stored_value;
+  if (prefs_->Exists(kPrefsCurrentResponseSignature) &&
+      prefs_->GetString(kPrefsCurrentResponseSignature, &stored_value)) {
+    SetResponseSignature(stored_value);
+  }
+}
+
+void PayloadState::SetResponseSignature(const string& response_signature) {
+  CHECK(prefs_);
+  response_signature_ = response_signature;
+  LOG(INFO) << "Current Response Signature = \n" << response_signature_;
+  prefs_->SetString(kPrefsCurrentResponseSignature, response_signature_);
+}
+
+void PayloadState::LoadPayloadAttemptNumber() {
+  SetPayloadAttemptNumber(GetPersistedValue(kPrefsPayloadAttemptNumber));
+}
+
+void PayloadState::LoadFullPayloadAttemptNumber() {
+  SetFullPayloadAttemptNumber(GetPersistedValue(
+      kPrefsFullPayloadAttemptNumber));
+}
+
+void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) {
+  CHECK(prefs_);
+  payload_attempt_number_ = payload_attempt_number;
+  LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number_;
+  prefs_->SetInt64(kPrefsPayloadAttemptNumber, payload_attempt_number_);
+}
+
+void PayloadState::SetFullPayloadAttemptNumber(
+    int full_payload_attempt_number) {
+  CHECK(prefs_);
+  full_payload_attempt_number_ = full_payload_attempt_number;
+  LOG(INFO) << "Full Payload Attempt Number = " << full_payload_attempt_number_;
+  prefs_->SetInt64(kPrefsFullPayloadAttemptNumber,
+      full_payload_attempt_number_);
+}
+
+void PayloadState::LoadUrlIndex() {
+  SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex));
+}
+
+void PayloadState::SetUrlIndex(uint32_t url_index) {
+  CHECK(prefs_);
+  url_index_ = url_index;
+  LOG(INFO) << "Current URL Index = " << url_index_;
+  prefs_->SetInt64(kPrefsCurrentUrlIndex, url_index_);
+
+  // Also update the download source, which is purely dependent on the
+  // current URL index alone.
+  UpdateCurrentDownloadSource();
+}
+
+void PayloadState::LoadScatteringWaitPeriod() {
+  SetScatteringWaitPeriod(
+      TimeDelta::FromSeconds(GetPersistedValue(kPrefsWallClockWaitPeriod)));
+}
+
+void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) {
+  CHECK(prefs_);
+  scattering_wait_period_ = wait_period;
+  LOG(INFO) << "Scattering Wait Period (seconds) = "
+            << scattering_wait_period_.InSeconds();
+  if (scattering_wait_period_.InSeconds() > 0) {
+    prefs_->SetInt64(kPrefsWallClockWaitPeriod,
+                     scattering_wait_period_.InSeconds());
+  } else {
+    prefs_->Delete(kPrefsWallClockWaitPeriod);
+  }
+}
+
+void PayloadState::LoadUrlSwitchCount() {
+  SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount));
+}
+
+void PayloadState::SetUrlSwitchCount(uint32_t url_switch_count) {
+  CHECK(prefs_);
+  url_switch_count_ = url_switch_count;
+  LOG(INFO) << "URL Switch Count = " << url_switch_count_;
+  prefs_->SetInt64(kPrefsUrlSwitchCount, url_switch_count_);
+}
+
+void PayloadState::LoadUrlFailureCount() {
+  SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount));
+}
+
+void PayloadState::SetUrlFailureCount(uint32_t url_failure_count) {
+  CHECK(prefs_);
+  url_failure_count_ = url_failure_count;
+  LOG(INFO) << "Current URL (Url" << GetUrlIndex()
+            << ")'s Failure Count = " << url_failure_count_;
+  prefs_->SetInt64(kPrefsCurrentUrlFailureCount, url_failure_count_);
+}
+
+void PayloadState::LoadBackoffExpiryTime() {
+  CHECK(prefs_);
+  int64_t stored_value;
+  if (!prefs_->Exists(kPrefsBackoffExpiryTime))
+    return;
+
+  if (!prefs_->GetInt64(kPrefsBackoffExpiryTime, &stored_value))
+    return;
+
+  Time stored_time = Time::FromInternalValue(stored_value);
+  if (stored_time > Time::Now() + TimeDelta::FromDays(kMaxBackoffDays)) {
+    LOG(ERROR) << "Invalid backoff expiry time ("
+               << utils::ToString(stored_time)
+               << ") in persisted state. Resetting.";
+    stored_time = Time();
+  }
+  SetBackoffExpiryTime(stored_time);
+}
+
+void PayloadState::SetBackoffExpiryTime(const Time& new_time) {
+  CHECK(prefs_);
+  backoff_expiry_time_ = new_time;
+  LOG(INFO) << "Backoff Expiry Time = "
+            << utils::ToString(backoff_expiry_time_);
+  prefs_->SetInt64(kPrefsBackoffExpiryTime,
+                   backoff_expiry_time_.ToInternalValue());
+}
+
+TimeDelta PayloadState::GetUpdateDuration() {
+  Time end_time = update_timestamp_end_.is_null()
+    ? system_state_->clock()->GetWallclockTime() :
+      update_timestamp_end_;
+  return end_time - update_timestamp_start_;
+}
+
+void PayloadState::LoadUpdateTimestampStart() {
+  int64_t stored_value;
+  Time stored_time;
+
+  CHECK(prefs_);
+
+  Time now = system_state_->clock()->GetWallclockTime();
+
+  if (!prefs_->Exists(kPrefsUpdateTimestampStart)) {
+    // The preference missing is not unexpected - in that case, just
+    // use the current time as start time
+    stored_time = now;
+  } else if (!prefs_->GetInt64(kPrefsUpdateTimestampStart, &stored_value)) {
+    LOG(ERROR) << "Invalid UpdateTimestampStart value. Resetting.";
+    stored_time = now;
+  } else {
+    stored_time = Time::FromInternalValue(stored_value);
+  }
+
+  // Sanity check: If the time read from disk is in the future
+  // (modulo some slack to account for possible NTP drift
+  // adjustments), something is fishy and we should report and
+  // reset.
+  TimeDelta duration_according_to_stored_time = now - stored_time;
+  if (duration_according_to_stored_time < -kDurationSlack) {
+    LOG(ERROR) << "The UpdateTimestampStart value ("
+               << utils::ToString(stored_time)
+               << ") in persisted state is "
+               << utils::FormatTimeDelta(duration_according_to_stored_time)
+               << " in the future. Resetting.";
+    stored_time = now;
+  }
+
+  SetUpdateTimestampStart(stored_time);
+}
+
+void PayloadState::SetUpdateTimestampStart(const Time& value) {
+  CHECK(prefs_);
+  update_timestamp_start_ = value;
+  prefs_->SetInt64(kPrefsUpdateTimestampStart,
+                   update_timestamp_start_.ToInternalValue());
+  LOG(INFO) << "Update Timestamp Start = "
+            << utils::ToString(update_timestamp_start_);
+}
+
+void PayloadState::SetUpdateTimestampEnd(const Time& value) {
+  update_timestamp_end_ = value;
+  LOG(INFO) << "Update Timestamp End = "
+            << utils::ToString(update_timestamp_end_);
+}
+
+TimeDelta PayloadState::GetUpdateDurationUptime() {
+  return update_duration_uptime_;
+}
+
+void PayloadState::LoadUpdateDurationUptime() {
+  int64_t stored_value;
+  TimeDelta stored_delta;
+
+  CHECK(prefs_);
+
+  if (!prefs_->Exists(kPrefsUpdateDurationUptime)) {
+    // The preference missing is not unexpected - in that case, just
+    // we'll use zero as the delta
+  } else if (!prefs_->GetInt64(kPrefsUpdateDurationUptime, &stored_value)) {
+    LOG(ERROR) << "Invalid UpdateDurationUptime value. Resetting.";
+    stored_delta = TimeDelta::FromSeconds(0);
+  } else {
+    stored_delta = TimeDelta::FromInternalValue(stored_value);
+  }
+
+  // Sanity-check: Uptime can never be greater than the wall-clock
+  // difference (modulo some slack). If it is, report and reset
+  // to the wall-clock difference.
+  TimeDelta diff = GetUpdateDuration() - stored_delta;
+  if (diff < -kDurationSlack) {
+    LOG(ERROR) << "The UpdateDurationUptime value ("
+               << utils::FormatTimeDelta(stored_delta)
+               << ") in persisted state is "
+               << utils::FormatTimeDelta(diff)
+               << " larger than the wall-clock delta. Resetting.";
+    stored_delta = update_duration_current_;
+  }
+
+  SetUpdateDurationUptime(stored_delta);
+}
+
+void PayloadState::LoadNumReboots() {
+  SetNumReboots(GetPersistedValue(kPrefsNumReboots));
+}
+
+void PayloadState::LoadRollbackVersion() {
+  CHECK(powerwash_safe_prefs_);
+  string rollback_version;
+  if (powerwash_safe_prefs_->GetString(kPrefsRollbackVersion,
+                                       &rollback_version)) {
+    SetRollbackVersion(rollback_version);
+  }
+}
+
+void PayloadState::SetRollbackVersion(const string& rollback_version) {
+  CHECK(powerwash_safe_prefs_);
+  LOG(INFO) << "Blacklisting version "<< rollback_version;
+  rollback_version_ = rollback_version;
+  powerwash_safe_prefs_->SetString(kPrefsRollbackVersion, rollback_version);
+}
+
+void PayloadState::SetUpdateDurationUptimeExtended(const TimeDelta& value,
+                                                   const Time& timestamp,
+                                                   bool use_logging) {
+  CHECK(prefs_);
+  update_duration_uptime_ = value;
+  update_duration_uptime_timestamp_ = timestamp;
+  prefs_->SetInt64(kPrefsUpdateDurationUptime,
+                   update_duration_uptime_.ToInternalValue());
+  if (use_logging) {
+    LOG(INFO) << "Update Duration Uptime = "
+              << utils::FormatTimeDelta(update_duration_uptime_);
+  }
+}
+
+void PayloadState::SetUpdateDurationUptime(const TimeDelta& value) {
+  Time now = system_state_->clock()->GetMonotonicTime();
+  SetUpdateDurationUptimeExtended(value, now, true);
+}
+
+void PayloadState::CalculateUpdateDurationUptime() {
+  Time now = system_state_->clock()->GetMonotonicTime();
+  TimeDelta uptime_since_last_update = now - update_duration_uptime_timestamp_;
+  TimeDelta new_uptime = update_duration_uptime_ + uptime_since_last_update;
+  // We're frequently called so avoid logging this write
+  SetUpdateDurationUptimeExtended(new_uptime, now, false);
+}
+
+string PayloadState::GetPrefsKey(const string& prefix, DownloadSource source) {
+  return prefix + "-from-" + utils::ToString(source);
+}
+
+void PayloadState::LoadCurrentBytesDownloaded(DownloadSource source) {
+  string key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+  SetCurrentBytesDownloaded(source, GetPersistedValue(key), true);
+}
+
+void PayloadState::SetCurrentBytesDownloaded(
+    DownloadSource source,
+    uint64_t current_bytes_downloaded,
+    bool log) {
+  CHECK(prefs_);
+
+  if (source >= kNumDownloadSources)
+    return;
+
+  // Update the in-memory value.
+  current_bytes_downloaded_[source] = current_bytes_downloaded;
+
+  string prefs_key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+  prefs_->SetInt64(prefs_key, current_bytes_downloaded);
+  LOG_IF(INFO, log) << "Current bytes downloaded for "
+                    << utils::ToString(source) << " = "
+                    << GetCurrentBytesDownloaded(source);
+}
+
+void PayloadState::LoadTotalBytesDownloaded(DownloadSource source) {
+  string key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+  SetTotalBytesDownloaded(source, GetPersistedValue(key), true);
+}
+
+void PayloadState::SetTotalBytesDownloaded(
+    DownloadSource source,
+    uint64_t total_bytes_downloaded,
+    bool log) {
+  CHECK(prefs_);
+
+  if (source >= kNumDownloadSources)
+    return;
+
+  // Update the in-memory value.
+  total_bytes_downloaded_[source] = total_bytes_downloaded;
+
+  // Persist.
+  string prefs_key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+  prefs_->SetInt64(prefs_key, total_bytes_downloaded);
+  LOG_IF(INFO, log) << "Total bytes downloaded for "
+                    << utils::ToString(source) << " = "
+                    << GetTotalBytesDownloaded(source);
+}
+
+void PayloadState::LoadNumResponsesSeen() {
+  SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen));
+}
+
+void PayloadState::SetNumResponsesSeen(int num_responses_seen) {
+  CHECK(prefs_);
+  num_responses_seen_ = num_responses_seen;
+  LOG(INFO) << "Num Responses Seen = " << num_responses_seen_;
+  prefs_->SetInt64(kPrefsNumResponsesSeen, num_responses_seen_);
+}
+
+void PayloadState::ReportUpdatesAbandonedEventCountMetric() {
+  string metric = "Installer.UpdatesAbandonedEventCount";
+  int value = num_responses_seen_ - 1;
+
+  // Do not send an "abandoned" event when 0 payloads were abandoned since the
+  // last successful update.
+  if (value == 0)
+    return;
+
+  LOG(INFO) << "Uploading " << value << " (count) for metric " <<  metric;
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       value,
+       0,    // min value
+       100,  // max value
+       kNumDefaultUmaBuckets);
+}
+
+void PayloadState::ComputeCandidateUrls() {
+  bool http_url_ok = true;
+
+  if (system_state_->hardware()->IsOfficialBuild()) {
+    const policy::DevicePolicy* policy = system_state_->device_policy();
+    if (policy && policy->GetHttpDownloadsEnabled(&http_url_ok) && !http_url_ok)
+      LOG(INFO) << "Downloads via HTTP Url are not enabled by device policy";
+  } else {
+    LOG(INFO) << "Allowing HTTP downloads for unofficial builds";
+    http_url_ok = true;
+  }
+
+  candidate_urls_.clear();
+  for (size_t i = 0; i < response_.payload_urls.size(); i++) {
+    string candidate_url = response_.payload_urls[i];
+    if (base::StartsWithASCII(candidate_url, "http://", false) && !http_url_ok)
+      continue;
+    candidate_urls_.push_back(candidate_url);
+    LOG(INFO) << "Candidate Url" << (candidate_urls_.size() - 1)
+              << ": " << candidate_url;
+  }
+
+  LOG(INFO) << "Found " << candidate_urls_.size() << " candidate URLs "
+            << "out of " << response_.payload_urls.size() << " URLs supplied";
+}
+
+void PayloadState::CreateSystemUpdatedMarkerFile() {
+  CHECK(prefs_);
+  int64_t value = system_state_->clock()->GetWallclockTime().ToInternalValue();
+  prefs_->SetInt64(kPrefsSystemUpdatedMarker, value);
+}
+
+void PayloadState::BootedIntoUpdate(TimeDelta time_to_reboot) {
+  // Send |time_to_reboot| as a UMA stat.
+  string metric = "Installer.TimeToRebootMinutes";
+  system_state_->metrics_lib()->SendToUMA(metric,
+                                          time_to_reboot.InMinutes(),
+                                          0,         // min: 0 minute
+                                          30*24*60,  // max: 1 month (approx)
+                                          kNumDefaultUmaBuckets);
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot)
+            << " for metric " <<  metric;
+
+  metric = metrics::kMetricTimeToRebootMinutes;
+  system_state_->metrics_lib()->SendToUMA(metric,
+                                          time_to_reboot.InMinutes(),
+                                          0,         // min: 0 minute
+                                          30*24*60,  // max: 1 month (approx)
+                                          kNumDefaultUmaBuckets);
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot)
+            << " for metric " <<  metric;
+}
+
+void PayloadState::UpdateEngineStarted() {
+  // Flush previous state from abnormal attempt failure, if any.
+  ReportAndClearPersistedAttemptMetrics();
+
+  // Avoid the UpdateEngineStarted actions if this is not the first time we
+  // run the update engine since reboot.
+  if (!system_state_->system_rebooted())
+    return;
+
+  // Figure out if we just booted into a new update
+  if (prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+    int64_t stored_value;
+    if (prefs_->GetInt64(kPrefsSystemUpdatedMarker, &stored_value)) {
+      Time system_updated_at = Time::FromInternalValue(stored_value);
+      if (!system_updated_at.is_null()) {
+        TimeDelta time_to_reboot =
+            system_state_->clock()->GetWallclockTime() - system_updated_at;
+        if (time_to_reboot.ToInternalValue() < 0) {
+          LOG(ERROR) << "time_to_reboot is negative - system_updated_at: "
+                     << utils::ToString(system_updated_at);
+        } else {
+          BootedIntoUpdate(time_to_reboot);
+        }
+      }
+    }
+    prefs_->Delete(kPrefsSystemUpdatedMarker);
+  }
+  // Check if it is needed to send metrics about a failed reboot into a new
+  // version.
+  ReportFailedBootIfNeeded();
+}
+
+void PayloadState::ReportFailedBootIfNeeded() {
+  // If the kPrefsTargetVersionInstalledFrom is present, a successfully applied
+  // payload was marked as ready immediately before the last reboot, and we
+  // need to check if such payload successfully rebooted or not.
+  if (prefs_->Exists(kPrefsTargetVersionInstalledFrom)) {
+    int64_t installed_from = 0;
+    if (!prefs_->GetInt64(kPrefsTargetVersionInstalledFrom, &installed_from)) {
+      LOG(ERROR) << "Error reading TargetVersionInstalledFrom on reboot.";
+      return;
+    }
+    if (static_cast<int>(installed_from) ==
+        utils::GetPartitionNumber(system_state_->hardware()->BootDevice())) {
+      // A reboot was pending, but the chromebook is again in the same
+      // BootDevice where the update was installed from.
+      int64_t target_attempt;
+      if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt)) {
+        LOG(ERROR) << "Error reading TargetVersionAttempt when "
+                      "TargetVersionInstalledFrom was present.";
+        target_attempt = 1;
+      }
+
+      // Report the UMA metric of the current boot failure.
+      string metric = "Installer.RebootToNewPartitionAttempt";
+
+      LOG(INFO) << "Uploading " << target_attempt
+                << " (count) for metric " <<  metric;
+      system_state_->metrics_lib()->SendToUMA(
+           metric,
+           target_attempt,
+           1,    // min value
+           50,   // max value
+           kNumDefaultUmaBuckets);
+
+      metric = metrics::kMetricFailedUpdateCount;
+      LOG(INFO) << "Uploading " << target_attempt
+                << " (count) for metric " <<  metric;
+      system_state_->metrics_lib()->SendToUMA(
+           metric,
+           target_attempt,
+           1,    // min value
+           50,   // max value
+           kNumDefaultUmaBuckets);
+    } else {
+      prefs_->Delete(kPrefsTargetVersionAttempt);
+      prefs_->Delete(kPrefsTargetVersionUniqueId);
+    }
+    prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+  }
+}
+
+void PayloadState::ExpectRebootInNewVersion(const string& target_version_uid) {
+  // Expect to boot into the new partition in the next reboot setting the
+  // TargetVersion* flags in the Prefs.
+  string stored_target_version_uid;
+  string target_version_id;
+  string target_partition;
+  int64_t target_attempt;
+
+  if (prefs_->Exists(kPrefsTargetVersionUniqueId) &&
+      prefs_->GetString(kPrefsTargetVersionUniqueId,
+                        &stored_target_version_uid) &&
+      stored_target_version_uid == target_version_uid) {
+    if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+      target_attempt = 0;
+  } else {
+    prefs_->SetString(kPrefsTargetVersionUniqueId, target_version_uid);
+    target_attempt = 0;
+  }
+  prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt + 1);
+
+  prefs_->SetInt64(kPrefsTargetVersionInstalledFrom,
+                    utils::GetPartitionNumber(
+                        system_state_->hardware()->BootDevice()));
+}
+
+void PayloadState::ResetUpdateStatus() {
+  // Remove the TargetVersionInstalledFrom pref so that if the machine is
+  // rebooted the next boot is not flagged as failed to rebooted into the
+  // new applied payload.
+  prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+
+  // Also decrement the attempt number if it exists.
+  int64_t target_attempt;
+  if (prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+    prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt-1);
+}
+
+int PayloadState::GetP2PNumAttempts() {
+  return p2p_num_attempts_;
+}
+
+void PayloadState::SetP2PNumAttempts(int value) {
+  p2p_num_attempts_ = value;
+  LOG(INFO) << "p2p Num Attempts = " << p2p_num_attempts_;
+  CHECK(prefs_);
+  prefs_->SetInt64(kPrefsP2PNumAttempts, value);
+}
+
+void PayloadState::LoadP2PNumAttempts() {
+  SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts));
+}
+
+Time PayloadState::GetP2PFirstAttemptTimestamp() {
+  return p2p_first_attempt_timestamp_;
+}
+
+void PayloadState::SetP2PFirstAttemptTimestamp(const Time& time) {
+  p2p_first_attempt_timestamp_ = time;
+  LOG(INFO) << "p2p First Attempt Timestamp = "
+            << utils::ToString(p2p_first_attempt_timestamp_);
+  CHECK(prefs_);
+  int64_t stored_value = time.ToInternalValue();
+  prefs_->SetInt64(kPrefsP2PFirstAttemptTimestamp, stored_value);
+}
+
+void PayloadState::LoadP2PFirstAttemptTimestamp() {
+  int64_t stored_value = GetPersistedValue(kPrefsP2PFirstAttemptTimestamp);
+  Time stored_time = Time::FromInternalValue(stored_value);
+  SetP2PFirstAttemptTimestamp(stored_time);
+}
+
+void PayloadState::P2PNewAttempt() {
+  CHECK(prefs_);
+  // Set timestamp, if it hasn't been set already
+  if (p2p_first_attempt_timestamp_.is_null()) {
+    SetP2PFirstAttemptTimestamp(system_state_->clock()->GetWallclockTime());
+  }
+  // Increase number of attempts
+  SetP2PNumAttempts(GetP2PNumAttempts() + 1);
+}
+
+bool PayloadState::P2PAttemptAllowed() {
+  if (p2p_num_attempts_ > kMaxP2PAttempts) {
+    LOG(INFO) << "Number of p2p attempts is " << p2p_num_attempts_
+              << " which is greater than "
+              << kMaxP2PAttempts
+              << " - disallowing p2p.";
+    return false;
+  }
+
+  if (!p2p_first_attempt_timestamp_.is_null()) {
+    Time now = system_state_->clock()->GetWallclockTime();
+    TimeDelta time_spent_attempting_p2p = now - p2p_first_attempt_timestamp_;
+    if (time_spent_attempting_p2p.InSeconds() < 0) {
+      LOG(ERROR) << "Time spent attempting p2p is negative"
+                 << " - disallowing p2p.";
+      return false;
+    }
+    if (time_spent_attempting_p2p.InSeconds() > kMaxP2PAttemptTimeSeconds) {
+      LOG(INFO) << "Time spent attempting p2p is "
+                << utils::FormatTimeDelta(time_spent_attempting_p2p)
+                << " which is greater than "
+                << utils::FormatTimeDelta(TimeDelta::FromSeconds(
+                       kMaxP2PAttemptTimeSeconds))
+                << " - disallowing p2p.";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_state.h b/payload_state.h
new file mode 100644
index 0000000..7def799
--- /dev/null
+++ b/payload_state.h
@@ -0,0 +1,568 @@
+// Copyright (c) 2012 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_STATE_H_
+#define UPDATE_ENGINE_PAYLOAD_STATE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/metrics.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// Encapsulates all the payload state required for download. This includes the
+// state necessary for handling multiple URLs in Omaha response, the backoff
+// state, etc. All state is persisted so that we use the most recently saved
+// value when resuming the update_engine process. All state is also cached in
+// memory so that we ensure we always make progress based on last known good
+// state even when there's any issue in reading/writing from the file system.
+class PayloadState : public PayloadStateInterface {
+ public:
+  PayloadState();
+  ~PayloadState() override {}
+
+  // Initializes a payload state object using the given global system state.
+  // It performs the initial loading of all persisted state into memory and
+  // dumps the initial state for debugging purposes.  Note: the other methods
+  // should be called only after calling Initialize on this object.
+  bool Initialize(SystemState* system_state);
+
+  // Implementation of PayloadStateInterface methods.
+  void SetResponse(const OmahaResponse& response) override;
+  void DownloadComplete() override;
+  void DownloadProgress(size_t count) override;
+  void UpdateResumed() override;
+  void UpdateRestarted() override;
+  void UpdateSucceeded() override;
+  void UpdateFailed(ErrorCode error) override;
+  void ResetUpdateStatus() override;
+  bool ShouldBackoffDownload() override;
+  void Rollback() override;
+  void ExpectRebootInNewVersion(const std::string& target_version_uid) override;
+  void SetUsingP2PForDownloading(bool value) override;
+
+  void SetUsingP2PForSharing(bool value) override {
+    using_p2p_for_sharing_ = value;
+  }
+
+  inline std::string GetResponseSignature() override {
+    return response_signature_;
+  }
+
+  inline int GetFullPayloadAttemptNumber() override {
+    return full_payload_attempt_number_;
+  }
+
+  inline int GetPayloadAttemptNumber() override {
+    return payload_attempt_number_;
+  }
+
+  inline std::string GetCurrentUrl() override {
+    return candidate_urls_.size() ? candidate_urls_[url_index_] : "";
+  }
+
+  inline uint32_t GetUrlFailureCount() override {
+    return url_failure_count_;
+  }
+
+  inline uint32_t GetUrlSwitchCount() override {
+    return url_switch_count_;
+  }
+
+  inline int GetNumResponsesSeen() override {
+    return num_responses_seen_;
+  }
+
+  inline base::Time GetBackoffExpiryTime() override {
+    return backoff_expiry_time_;
+  }
+
+  base::TimeDelta GetUpdateDuration() override;
+
+  base::TimeDelta GetUpdateDurationUptime() override;
+
+  inline uint64_t GetCurrentBytesDownloaded(DownloadSource source) override {
+    return source < kNumDownloadSources ? current_bytes_downloaded_[source] : 0;
+  }
+
+  inline uint64_t GetTotalBytesDownloaded(DownloadSource source) override {
+    return source < kNumDownloadSources ? total_bytes_downloaded_[source] : 0;
+  }
+
+  inline uint32_t GetNumReboots() override {
+    return num_reboots_;
+  }
+
+  void UpdateEngineStarted() override;
+
+  inline std::string GetRollbackVersion() override {
+    return rollback_version_;
+  }
+
+  int GetP2PNumAttempts() override;
+  base::Time GetP2PFirstAttemptTimestamp() override;
+  void P2PNewAttempt() override;
+  bool P2PAttemptAllowed() override;
+
+  bool GetUsingP2PForDownloading() const override {
+    return using_p2p_for_downloading_;
+  }
+
+  bool GetUsingP2PForSharing() const override {
+    return using_p2p_for_sharing_;
+  }
+
+  base::TimeDelta GetScatteringWaitPeriod() override {
+    return scattering_wait_period_;
+  }
+
+  void SetScatteringWaitPeriod(base::TimeDelta wait_period) override;
+
+  void SetP2PUrl(const std::string& url) override {
+    p2p_url_ = url;
+  }
+
+  std::string GetP2PUrl() const override {
+    return p2p_url_;
+  }
+
+ private:
+  enum class AttemptType {
+    kUpdate,
+    kRollback,
+  };
+
+  friend class PayloadStateTest;
+  FRIEND_TEST(PayloadStateTest, RebootAfterUpdateFailedMetric);
+  FRIEND_TEST(PayloadStateTest, RebootAfterUpdateSucceed);
+  FRIEND_TEST(PayloadStateTest, RebootAfterCanceledUpdate);
+  FRIEND_TEST(PayloadStateTest, RollbackVersion);
+  FRIEND_TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs);
+
+  // Helper called when an attempt has begun, is called by
+  // UpdateResumed(), UpdateRestarted() and Rollback().
+  void AttemptStarted(AttemptType attempt_type);
+
+  // Increments the payload attempt number used for metrics.
+  void IncrementPayloadAttemptNumber();
+
+  // Increments the payload attempt number which governs the backoff behavior
+  // at the time of the next update check.
+  void IncrementFullPayloadAttemptNumber();
+
+  // Advances the current URL index to the next available one. If all URLs have
+  // been exhausted during the current payload download attempt (as indicated
+  // by the payload attempt number), then it will increment the payload attempt
+  // number and wrap around again with the first URL in the list. This also
+  // updates the URL switch count, if needed.
+  void IncrementUrlIndex();
+
+  // Increments the failure count of the current URL. If the configured max
+  // failure count is reached for this URL, it advances the current URL index
+  // to the next URL and resets the failure count for that URL.
+  void IncrementFailureCount();
+
+  // Updates the backoff expiry time exponentially based on the current
+  // payload attempt number.
+  void UpdateBackoffExpiryTime();
+
+  // Updates the value of current download source based on the current URL
+  // index. If the download source is not one of the known sources, it's set
+  // to kNumDownloadSources.
+  void UpdateCurrentDownloadSource();
+
+  // Updates the various metrics corresponding with the given number of bytes
+  // that were downloaded recently.
+  void UpdateBytesDownloaded(size_t count);
+
+  // Calculates the PayloadType we're using.
+  PayloadType CalculatePayloadType();
+
+  // Collects and reports the various metrics related to an update attempt.
+  void CollectAndReportAttemptMetrics(ErrorCode code);
+
+  // Persists values related to the UpdateEngine.Attempt.* metrics so
+  // we can identify later if an update attempt ends abnormally.
+  void PersistAttemptMetrics();
+
+  // Clears persistent state previously set using AttemptMetricsPersist().
+  void ClearPersistedAttemptMetrics();
+
+  // Checks if persistent state previously set using AttemptMetricsPersist()
+  // exists and, if so, emits it with |attempt_result| set to
+  // metrics::AttemptResult::kAbnormalTermination.
+  void ReportAndClearPersistedAttemptMetrics();
+
+  // Collects and reports the various metrics related to a successful update.
+  void CollectAndReportSuccessfulUpdateMetrics();
+
+  // Checks if we were expecting to be running in the new version but the
+  // boot into the new version failed for some reason. If that's the case, an
+  // UMA metric is sent reporting the number of attempts the same applied
+  // payload was attempted to reboot. This function is called by UpdateAttempter
+  // every time the update engine starts and there's no reboot pending.
+  void ReportFailedBootIfNeeded();
+
+  // Resets all the persisted state values which are maintained relative to the
+  // current response signature. The response signature itself is not reset.
+  void ResetPersistedState();
+
+  // Resets the appropriate state related to download sources that need to be
+  // reset on a new update.
+  void ResetDownloadSourcesOnNewUpdate();
+
+  // Returns the persisted value from prefs_ for the given key. It also
+  // validates that the value returned is non-negative.
+  int64_t GetPersistedValue(const std::string& key);
+
+  // Calculates the response "signature", which is basically a string composed
+  // of the subset of the fields in the current response that affect the
+  // behavior of the PayloadState.
+  std::string CalculateResponseSignature();
+
+  // Initializes the current response signature from the persisted state.
+  void LoadResponseSignature();
+
+  // Sets the response signature to the given value. Also persists the value
+  // being set so that we resume from the save value in case of a process
+  // restart.
+  void SetResponseSignature(const std::string& response_signature);
+
+  // Initializes the payload attempt number from the persisted state.
+  void LoadPayloadAttemptNumber();
+
+  // Initializes the payload attempt number for full payloads from the persisted
+  // state.
+  void LoadFullPayloadAttemptNumber();
+
+  // Sets the payload attempt number to the given value. Also persists the
+  // value being set so that we resume from the same value in case of a process
+  // restart.
+  void SetPayloadAttemptNumber(int payload_attempt_number);
+
+  // Sets the payload attempt number for full updates to the given value. Also
+  // persists the value being set so that we resume from the same value in case
+  // of a process restart.
+  void SetFullPayloadAttemptNumber(int payload_attempt_number);
+
+  // Initializes the current URL index from the persisted state.
+  void LoadUrlIndex();
+
+  // Sets the current URL index to the given value. Also persists the value
+  // being set so that we resume from the same value in case of a process
+  // restart.
+  void SetUrlIndex(uint32_t url_index);
+
+  // Initializes the current URL's failure count from the persisted stae.
+  void LoadUrlFailureCount();
+
+  // Sets the current URL's failure count to the given value. Also persists the
+  // value being set so that we resume from the same value in case of a process
+  // restart.
+  void SetUrlFailureCount(uint32_t url_failure_count);
+
+  // Sets |url_switch_count_| to the given value and persists the value.
+  void SetUrlSwitchCount(uint32_t url_switch_count);
+
+  // Initializes |url_switch_count_| from the persisted stae.
+  void LoadUrlSwitchCount();
+
+  // Initializes the backoff expiry time from the persisted state.
+  void LoadBackoffExpiryTime();
+
+  // Sets the backoff expiry time to the given value. Also persists the value
+  // being set so that we resume from the same value in case of a process
+  // restart.
+  void SetBackoffExpiryTime(const base::Time& new_time);
+
+  // Initializes |update_timestamp_start_| from the persisted state.
+  void LoadUpdateTimestampStart();
+
+  // Sets |update_timestamp_start_| to the given value and persists the value.
+  void SetUpdateTimestampStart(const base::Time& value);
+
+  // Sets |update_timestamp_end_| to the given value. This is not persisted
+  // as it happens at the end of the update process where state is deleted
+  // anyway.
+  void SetUpdateTimestampEnd(const base::Time& value);
+
+  // Initializes |update_duration_uptime_| from the persisted state.
+  void LoadUpdateDurationUptime();
+
+  // Helper method used in SetUpdateDurationUptime() and
+  // CalculateUpdateDurationUptime().
+  void SetUpdateDurationUptimeExtended(const base::TimeDelta& value,
+                                       const base::Time& timestamp,
+                                       bool use_logging);
+
+  // Sets |update_duration_uptime_| to the given value and persists
+  // the value and sets |update_duration_uptime_timestamp_| to the
+  // current monotonic time.
+  void SetUpdateDurationUptime(const base::TimeDelta& value);
+
+  // Adds the difference between current monotonic time and
+  // |update_duration_uptime_timestamp_| to |update_duration_uptime_| and
+  // sets |update_duration_uptime_timestamp_| to current monotonic time.
+  void CalculateUpdateDurationUptime();
+
+  // Returns the full key for a download source given the prefix.
+  std::string GetPrefsKey(const std::string& prefix, DownloadSource source);
+
+  // Loads the number of bytes that have been currently downloaded through the
+  // previous attempts from the persisted state for the given source. It's
+  // reset to 0 everytime we begin a full update and is continued from previous
+  // attempt if we're resuming the update.
+  void LoadCurrentBytesDownloaded(DownloadSource source);
+
+  // Sets the number of bytes that have been currently downloaded for the
+  // given source. This value is also persisted.
+  void SetCurrentBytesDownloaded(DownloadSource source,
+                                 uint64_t current_bytes_downloaded,
+                                 bool log);
+
+  // Loads the total number of bytes that have been downloaded (since the last
+  // successful update) from the persisted state for the given source. It's
+  // reset to 0 everytime we successfully apply an update and counts the bytes
+  // downloaded for both successful and failed attempts since then.
+  void LoadTotalBytesDownloaded(DownloadSource source);
+
+  // Sets the total number of bytes that have been downloaded so far for the
+  // given source. This value is also persisted.
+  void SetTotalBytesDownloaded(DownloadSource source,
+                               uint64_t total_bytes_downloaded,
+                               bool log);
+
+  // Loads the blacklisted version from our prefs file.
+  void LoadRollbackVersion();
+
+  // Blacklists this version from getting AU'd to until we receive a new update
+  // response.
+  void SetRollbackVersion(const std::string& rollback_version);
+
+  // Clears any blacklisted version.
+  void ResetRollbackVersion();
+
+  inline uint32_t GetUrlIndex() {
+    return url_index_;
+  }
+
+  // Computes the list of candidate URLs from the total list of payload URLs in
+  // the Omaha response.
+  void ComputeCandidateUrls();
+
+  // Sets |num_responses_seen_| and persist it to disk.
+  void SetNumResponsesSeen(int num_responses_seen);
+
+  // Initializes |num_responses_seen_| from persisted state.
+  void LoadNumResponsesSeen();
+
+  // Reports metric conveying how many times updates were abandoned since
+  // the last update was applied. The difference between this metric and the
+  // previous ReportUpdatesAbandonedCountMetric() one is that this metric is
+  // reported every time an update is abandoned, as oposed to the mentioned
+  // metric that is reported once the new update was applied.
+  void ReportUpdatesAbandonedEventCountMetric();
+
+  // Initializes |num_reboots_| from the persisted state.
+  void LoadNumReboots();
+
+  // Sets |num_reboots| for the update attempt. Also persists the
+  // value being set so that we resume from the same value in case of a process
+  // restart.
+  void SetNumReboots(uint32_t num_reboots);
+
+  // Checks to see if the device rebooted since the last call and if so
+  // increments num_reboots.
+  void UpdateNumReboots();
+
+  // Writes the current wall-clock time to the kPrefsSystemUpdatedMarker
+  // state variable.
+  void CreateSystemUpdatedMarkerFile();
+
+  // Called at program startup if the device booted into a new update.
+  // The |time_to_reboot| parameter contains the (wall-clock) duration
+  // from when the update successfully completed (the value written
+  // into the kPrefsSystemUpdatedMarker state variable) until the device
+  // was booted into the update (current wall-clock time).
+  void BootedIntoUpdate(base::TimeDelta time_to_reboot);
+
+  // Loads the |kPrefsP2PFirstAttemptTimestamp| state variable from disk
+  // into |p2p_first_attempt_timestamp_|.
+  void LoadP2PFirstAttemptTimestamp();
+
+  // Loads the |kPrefsP2PNumAttempts| state variable into |p2p_num_attempts_|.
+  void LoadP2PNumAttempts();
+
+  // Sets the |kPrefsP2PNumAttempts| state variable to |value|.
+  void SetP2PNumAttempts(int value);
+
+  // Sets the |kPrefsP2PFirstAttemptTimestamp| state variable to |time|.
+  void SetP2PFirstAttemptTimestamp(const base::Time& time);
+
+  // Loads the persisted scattering wallclock-based wait period.
+  void LoadScatteringWaitPeriod();
+
+  // The global state of the system.
+  SystemState* system_state_;
+
+  // Interface object with which we read/write persisted state. This must
+  // be set by calling the Initialize method before calling any other method.
+  PrefsInterface* prefs_;
+
+  // Interface object with which we read/write persisted state. This must
+  // be set by calling the Initialize method before calling any other method.
+  // This object persists across powerwashes.
+  PrefsInterface* powerwash_safe_prefs_;
+
+  // This is the current response object from Omaha.
+  OmahaResponse response_;
+
+  // Whether P2P is being used for downloading and sharing.
+  bool using_p2p_for_downloading_;
+  bool using_p2p_for_sharing_;
+
+  // Stores the P2P download URL, if one is used.
+  std::string p2p_url_;
+
+  // The cached value of |kPrefsP2PFirstAttemptTimestamp|.
+  base::Time p2p_first_attempt_timestamp_;
+
+  // The cached value of |kPrefsP2PNumAttempts|.
+  int p2p_num_attempts_;
+
+  // This stores a "signature" of the current response. The signature here
+  // refers to a subset of the current response from Omaha.  Each update to
+  // this value is persisted so we resume from the same value in case of a
+  // process restart.
+  std::string response_signature_;
+
+  // The number of times we've tried to download the payload. This is
+  // incremented each time we download the payload successsfully or when we
+  // exhaust all failure limits for all URLs and are about to wrap around back
+  // to the first URL.  Each update to this value is persisted so we resume from
+  // the same value in case of a process restart.
+  int payload_attempt_number_;
+
+  // The number of times we've tried to download the payload in full. This is
+  // incremented each time we download the payload in full successsfully or
+  // when we exhaust all failure limits for all URLs and are about to wrap
+  // around back to the first URL.  Each update to this value is persisted so
+  // we resume from the same value in case of a process restart.
+  int full_payload_attempt_number_;
+
+  // The index of the current URL.  This type is different from the one in the
+  // accessor methods because PrefsInterface supports only int64_t but we want
+  // to provide a stronger abstraction of uint32_t.  Each update to this value
+  // is persisted so we resume from the same value in case of a process
+  // restart.
+  int64_t url_index_;
+
+  // The count of failures encountered in the current attempt to download using
+  // the current URL (specified by url_index_).  Each update to this value is
+  // persisted so we resume from the same value in case of a process restart.
+  int64_t url_failure_count_;
+
+  // The number of times we've switched URLs.
+  int32_t url_switch_count_;
+
+  // The current download source based on the current URL. This value is
+  // not persisted as it can be recomputed everytime we update the URL.
+  // We're storing this so as not to recompute this on every few bytes of
+  // data we read from the socket.
+  DownloadSource current_download_source_;
+
+  // The number of different Omaha responses seen. Increases every time
+  // a new response is seen. Resets to 0 only when the system has been
+  // successfully updated.
+  int num_responses_seen_;
+
+  // The number of system reboots during an update attempt. Technically since
+  // we don't go out of our way to not update it when not attempting an update,
+  // also records the number of reboots before the next update attempt starts.
+  uint32_t num_reboots_;
+
+  // The timestamp until which we've to wait before attempting to download the
+  // payload again, so as to backoff repeated downloads.
+  base::Time backoff_expiry_time_;
+
+  // The most recently calculated value of the update duration.
+  base::TimeDelta update_duration_current_;
+
+  // The point in time (wall-clock) that the update was started.
+  base::Time update_timestamp_start_;
+
+  // The point in time (wall-clock) that the update ended. If the update
+  // is still in progress, this is set to the Epoch (e.g. 0).
+  base::Time update_timestamp_end_;
+
+  // The update duration uptime
+  base::TimeDelta update_duration_uptime_;
+
+  // The monotonic time when |update_duration_uptime_| was last set
+  base::Time update_duration_uptime_timestamp_;
+
+  // The number of bytes that have been downloaded for each source for each new
+  // update attempt. If we resume an update, we'll continue from the previous
+  // value, but if we get a new response or if the previous attempt failed,
+  // we'll reset this to 0 to start afresh. Each update to this value is
+  // persisted so we resume from the same value in case of a process restart.
+  // The extra index in the array is to no-op accidental access in case the
+  // return value from GetCurrentDownloadSource is used without validation.
+  uint64_t current_bytes_downloaded_[kNumDownloadSources + 1];
+
+  // The number of bytes that have been downloaded for each source since the
+  // the last successful update. This is used to compute the overhead we incur.
+  // Each update to this value is persisted so we resume from the same value in
+  // case of a process restart.
+  // The extra index in the array is to no-op accidental access in case the
+  // return value from GetCurrentDownloadSource is used without validation.
+  uint64_t total_bytes_downloaded_[kNumDownloadSources + 1];
+
+  // A small timespan used when comparing wall-clock times for coping
+  // with the fact that clocks drift and consequently are adjusted
+  // (either forwards or backwards) via NTP.
+  static const base::TimeDelta kDurationSlack;
+
+  // The ordered list of the subset of payload URL candidates which are
+  // allowed as per device policy.
+  std::vector<std::string> candidate_urls_;
+
+  // This stores a blacklisted version set as part of rollback. When we rollback
+  // we store the version of the os from which we are rolling back from in order
+  // to guarantee that we do not re-update to it on the next au attempt after
+  // reboot.
+  std::string rollback_version_;
+
+  // The number of bytes downloaded per attempt.
+  int64_t attempt_num_bytes_downloaded_;
+
+  // The boot time when the attempt was started.
+  base::Time attempt_start_time_boot_;
+
+  // The monotonic time when the attempt was started.
+  base::Time attempt_start_time_monotonic_;
+
+  // The connection type when the attempt started.
+  metrics::ConnectionType attempt_connection_type_;
+
+  // Whether we're currently rolling back.
+  AttemptType attempt_type_;
+
+  // The current scattering wallclock-based wait period.
+  base::TimeDelta scattering_wait_period_;
+
+  DISALLOW_COPY_AND_ASSIGN(PayloadState);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_STATE_H_
diff --git a/payload_state_interface.h b/payload_state_interface.h
new file mode 100644
index 0000000..95344c6
--- /dev/null
+++ b/payload_state_interface.h
@@ -0,0 +1,187 @@
+// Copyright (c) 2012 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_STATE_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
+
+#include <string>
+
+#include "update_engine/action_processor.h"
+#include "update_engine/constants.h"
+#include "update_engine/omaha_response.h"
+
+namespace chromeos_update_engine {
+
+// Describes the methods that need to be implemented by the PayloadState class.
+// This interface has been carved out to support mocking of the PayloadState
+// object.
+class PayloadStateInterface {
+ public:
+  virtual ~PayloadStateInterface() = default;
+
+  // Sets the internal payload state based on the given Omaha response. This
+  // response could be the same or different from the one for which we've stored
+  // the internal state. If it's different, then this method resets all the
+  // internal state corresponding to the old response. Since the Omaha response
+  // has a lot of fields that are not related to payload state, it uses only
+  // a subset of the fields in the Omaha response to compare equality.
+  virtual void SetResponse(const OmahaResponse& response) = 0;
+
+  // This method should be called whenever we have completed downloading all
+  // the bytes of a payload and have verified that its size and hash match the
+  // expected values. We use this notificaiton to increment the payload attempt
+  // number so that the throttle the next attempt to download the same payload
+  // (in case there's an error in subsequent steps such as post-install)
+  // appropriately.
+  virtual void DownloadComplete() = 0;
+
+  // This method should be called whenever we receive new bytes from the
+  // network for the current payload. We use this notification to reset the
+  // failure count for a given URL since receipt of some bytes means we are
+  // able to make forward progress with the current URL.
+  virtual void DownloadProgress(size_t count) = 0;
+
+  // This method should be called every time we resume an update attempt.
+  virtual void UpdateResumed() = 0;
+
+  // This method should be called every time we begin a new update. This method
+  // should not be called when we resume an update from the previously
+  // downloaded point. This is used to reset the metrics for each new update.
+  virtual void UpdateRestarted() = 0;
+
+  // This method should be called once after an update attempt succeeds. This
+  // is when the relevant UMA metrics that are tracked on a per-update-basis
+  // are uploaded to the UMA server.
+  virtual void UpdateSucceeded() = 0;
+
+  // This method should be called whenever an update attempt fails with the
+  // given error code. We use this notification to update the payload state
+  // depending on the type of the error that happened.
+  virtual void UpdateFailed(ErrorCode error) = 0;
+
+  // This method should be called whenever a succeeded update is canceled, and
+  // thus can only be called after UpdateSucceeded(). This is currently used
+  // only for manual testing using the update_engine_client.
+  virtual void ResetUpdateStatus() = 0;
+
+  // This method should be called every time we initiate a Rollback.
+  virtual void Rollback() = 0;
+
+  // Sets the expectations to boot into the new version in the next reboot.
+  // This function is called every time a new update is marked as ready by
+  // UpdateSuccess(). |target_version_uid| is an unique identifier of the
+  // applied payload. It can be any string, as long as the same string is used
+  // for the same payload.
+  virtual void ExpectRebootInNewVersion(
+      const std::string& target_version_uid) = 0;
+
+  // Sets whether P2P is being used to download the update payload. This
+  // is used to keep track of download sources being used and should be called
+  // before the transfer begins.
+  virtual void SetUsingP2PForDownloading(bool value) = 0;
+
+  // Sets whether P2P is being used for sharing the update payloads.
+  virtual void SetUsingP2PForSharing(bool value) = 0;
+
+  // Returns true if we should backoff the current download attempt.
+  // False otherwise.
+  virtual bool ShouldBackoffDownload() = 0;
+
+  // Returns the currently stored response "signature". The signature  is a
+  // subset of fields that are of interest to the PayloadState behavior.
+  virtual std::string GetResponseSignature() = 0;
+
+  // Returns the payload attempt number.
+  virtual int GetPayloadAttemptNumber() = 0;
+
+  // Returns the payload attempt number of the attempted full payload. Returns
+  // 0 for delta payloads.
+  virtual int GetFullPayloadAttemptNumber() = 0;
+
+  // Returns the current URL. Returns an empty string if there's no valid URL.
+  virtual std::string GetCurrentUrl() = 0;
+
+  // Returns the current URL's failure count.
+  virtual uint32_t GetUrlFailureCount() = 0;
+
+  // Returns the total number of times a new URL has been switched to
+  // for the current response.
+  virtual uint32_t GetUrlSwitchCount() = 0;
+
+  // Returns the total number of different responses seen since the
+  // last successful update.
+  virtual int GetNumResponsesSeen() = 0;
+
+  // Returns the expiry time for the current backoff period.
+  virtual base::Time GetBackoffExpiryTime() = 0;
+
+  // Returns the elapsed time used for this update, including time
+  // where the device is powered off and sleeping. If the
+  // update has not completed, returns the time spent so far.
+  virtual base::TimeDelta GetUpdateDuration() = 0;
+
+  // Returns the time used for this update not including time when
+  // the device is powered off or sleeping. If the update has not
+  // completed, returns the time spent so far.
+  virtual base::TimeDelta GetUpdateDurationUptime() = 0;
+
+  // Returns the number of bytes that have been downloaded for each source for
+  // each new update attempt. If we resume an update, we'll continue from the
+  // previous value, but if we get a new response or if the previous attempt
+  // failed, we'll reset this to 0 to start afresh.
+  virtual uint64_t GetCurrentBytesDownloaded(DownloadSource source) = 0;
+
+  // Returns the total number of bytes that have been downloaded for each
+  // source since the the last successful update. This is used to compute the
+  // overhead we incur.
+  virtual uint64_t GetTotalBytesDownloaded(DownloadSource source) = 0;
+
+  // Returns the reboot count for this update attempt.
+  virtual uint32_t GetNumReboots() = 0;
+
+  // Called at update_engine startup to do various house-keeping.
+  virtual void UpdateEngineStarted() = 0;
+
+  // Returns the version from before a rollback if our last update was a
+  // rollback.
+  virtual std::string GetRollbackVersion() = 0;
+
+  // Returns the value of number of attempts we've attempted to
+  // download the payload via p2p.
+  virtual int GetP2PNumAttempts() = 0;
+
+  // Returns the value of timestamp of the first time we've attempted
+  // to download the payload via p2p.
+  virtual base::Time GetP2PFirstAttemptTimestamp() = 0;
+
+  // Should be called every time we decide to use p2p for an update
+  // attempt. This is used to increase the p2p attempt counter and
+  // set the timestamp for first attempt.
+  virtual void P2PNewAttempt() = 0;
+
+  // Returns |true| if we are allowed to continue using p2p for
+  // downloading and |false| otherwise. This is done by recording
+  // and examining how many attempts have been done already as well
+  // as when the first attempt was.
+  virtual bool P2PAttemptAllowed() = 0;
+
+  // Gets the values previously set with SetUsingP2PForDownloading() and
+  // SetUsingP2PForSharing().
+  virtual bool GetUsingP2PForDownloading() const = 0;
+  virtual bool GetUsingP2PForSharing() const = 0;
+
+  // Returns the current (persisted) scattering wallclock-based wait period.
+  virtual base::TimeDelta GetScatteringWaitPeriod() = 0;
+
+  // Sets and persists the scattering wallclock-based wait period.
+  virtual void SetScatteringWaitPeriod(base::TimeDelta wait_period) = 0;
+
+  // Sets/gets the P2P download URL, if one is to be used.
+  virtual void SetP2PUrl(const std::string& url) = 0;
+  virtual std::string GetP2PUrl() const = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
new file mode 100644
index 0000000..08ed398
--- /dev/null
+++ b/payload_state_unittest.cc
@@ -0,0 +1,1747 @@
+// Copyright (c) 2012 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_state.h"
+
+#include <glib.h>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+const char* kCurrentBytesDownloadedFromHttps =
+  "current-bytes-downloaded-from-HttpsServer";
+const char* kTotalBytesDownloadedFromHttps =
+  "total-bytes-downloaded-from-HttpsServer";
+const char* kCurrentBytesDownloadedFromHttp =
+  "current-bytes-downloaded-from-HttpServer";
+const char* kTotalBytesDownloadedFromHttp =
+  "total-bytes-downloaded-from-HttpServer";
+const char* kCurrentBytesDownloadedFromHttpPeer =
+  "current-bytes-downloaded-from-HttpPeer";
+const char* kTotalBytesDownloadedFromHttpPeer =
+  "total-bytes-downloaded-from-HttpPeer";
+
+static void SetupPayloadStateWith2Urls(string hash,
+                                       bool http_enabled,
+                                       PayloadState* payload_state,
+                                       OmahaResponse* response) {
+  response->payload_urls.clear();
+  response->payload_urls.push_back("http://test");
+  response->payload_urls.push_back("https://test");
+  response->size = 523456789;
+  response->hash = hash;
+  response->metadata_size = 558123;
+  response->metadata_signature = "metasign";
+  response->max_failure_count_per_url = 3;
+  payload_state->SetResponse(*response);
+  string stored_response_sign = payload_state->GetResponseSignature();
+
+  string expected_url_https_only =
+      "NumURLs = 1\n"
+      "Candidate Url0 = https://test\n";
+
+  string expected_urls_both =
+      "NumURLs = 2\n"
+      "Candidate Url0 = http://test\n"
+      "Candidate Url1 = https://test\n";
+
+  string expected_response_sign =
+      (http_enabled ? expected_urls_both : expected_url_https_only) +
+      base::StringPrintf("Payload Size = 523456789\n"
+                         "Payload Sha256 Hash = %s\n"
+                         "Metadata Size = 558123\n"
+                         "Metadata Signature = metasign\n"
+                         "Is Delta Payload = %d\n"
+                         "Max Failure Count Per Url = %d\n"
+                         "Disable Payload Backoff = %d\n",
+                         hash.c_str(),
+                         response->is_delta_payload,
+                         response->max_failure_count_per_url,
+                         response->disable_payload_backoff);
+  EXPECT_EQ(expected_response_sign, stored_response_sign);
+}
+
+class PayloadStateTest : public ::testing::Test { };
+
+TEST(PayloadStateTest, DidYouAddANewErrorCode) {
+  if (static_cast<int>(ErrorCode::kUmaReportedMax) != 48) {
+    LOG(ERROR) << "The following failure is intentional. If you added a new "
+               << "ErrorCode enum value, make sure to add it to the "
+               << "PayloadState::UpdateFailed method and then update this test "
+               << "to the new value of ErrorCode::kUmaReportedMax, which is "
+               << ErrorCode::kUmaReportedMax;
+    EXPECT_FALSE("Please see the log line above");
+  }
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithEmptyResponse) {
+  OmahaResponse response;
+  FakeSystemState fake_system_state;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0)).Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1));
+  PayloadState payload_state;
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  payload_state.SetResponse(response);
+  string stored_response_sign = payload_state.GetResponseSignature();
+  string expected_response_sign = "NumURLs = 0\n"
+                                  "Payload Size = 0\n"
+                                  "Payload Sha256 Hash = \n"
+                                  "Metadata Size = 0\n"
+                                  "Metadata Signature = \n"
+                                  "Is Delta Payload = 0\n"
+                                  "Max Failure Count Per Url = 0\n"
+                                  "Disable Payload Backoff = 0\n";
+  EXPECT_EQ(expected_response_sign, stored_response_sign);
+  EXPECT_EQ("", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+  EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) {
+  OmahaResponse response;
+  response.payload_urls.push_back("https://single.url.test");
+  response.size = 123456789;
+  response.hash = "hash";
+  response.metadata_size = 58123;
+  response.metadata_signature = "msign";
+  FakeSystemState fake_system_state;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+      .Times(AtLeast(1));
+  PayloadState payload_state;
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  payload_state.SetResponse(response);
+  string stored_response_sign = payload_state.GetResponseSignature();
+  string expected_response_sign = "NumURLs = 1\n"
+                                  "Candidate Url0 = https://single.url.test\n"
+                                  "Payload Size = 123456789\n"
+                                  "Payload Sha256 Hash = hash\n"
+                                  "Metadata Size = 58123\n"
+                                  "Metadata Signature = msign\n"
+                                  "Is Delta Payload = 0\n"
+                                  "Max Failure Count Per Url = 0\n"
+                                  "Disable Payload Backoff = 0\n";
+  EXPECT_EQ(expected_response_sign, stored_response_sign);
+  EXPECT_EQ("https://single.url.test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+  EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) {
+  OmahaResponse response;
+  response.payload_urls.push_back("http://multiple.url.test");
+  response.payload_urls.push_back("https://multiple.url.test");
+  response.size = 523456789;
+  response.hash = "rhash";
+  response.metadata_size = 558123;
+  response.metadata_signature = "metasign";
+  FakeSystemState fake_system_state;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+      .Times(AtLeast(1));
+
+  PayloadState payload_state;
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  payload_state.SetResponse(response);
+  string stored_response_sign = payload_state.GetResponseSignature();
+  string expected_response_sign = "NumURLs = 2\n"
+                                  "Candidate Url0 = http://multiple.url.test\n"
+                                  "Candidate Url1 = https://multiple.url.test\n"
+                                  "Payload Size = 523456789\n"
+                                  "Payload Sha256 Hash = rhash\n"
+                                  "Metadata Size = 558123\n"
+                                  "Metadata Signature = metasign\n"
+                                  "Is Delta Payload = 0\n"
+                                  "Max Failure Count Per Url = 0\n"
+                                  "Disable Payload Backoff = 0\n";
+  EXPECT_EQ(expected_response_sign, stored_response_sign);
+  EXPECT_EQ("http://multiple.url.test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+  EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, CanAdvanceUrlIndexCorrectly) {
+  OmahaResponse response;
+  FakeSystemState fake_system_state;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+  PayloadState payload_state;
+
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+  // Payload attempt should start with 0 and then advance to 1.
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(2));
+
+  // Reboots will be set
+  EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, _)).Times(AtLeast(1));
+
+  // Url index should go from 0 to 1 twice.
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(1));
+
+  // Failure count should be called each times url index is set, so that's
+  // 4 times for this test.
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+    .Times(AtLeast(4));
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // This does a SetResponse which causes all the states to be set to 0 for
+  // the first time.
+  SetupPayloadStateWith2Urls("Hash1235", true, &payload_state, &response);
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+  // Verify that on the first error, the URL index advances to 1.
+  ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+  payload_state.UpdateFailed(error);
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+  // Verify that on the next error, the URL index wraps around to 0.
+  payload_state.UpdateFailed(error);
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+  // Verify that on the next error, it again advances to 1.
+  payload_state.UpdateFailed(error);
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+  // Verify that we switched URLs three times
+  EXPECT_EQ(3, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NewResponseResetsPayloadState) {
+  OmahaResponse response;
+  FakeSystemState fake_system_state;
+  PayloadState payload_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+
+  // The first response doesn't send an abandoned event.
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.UpdatesAbandonedEventCount", 0, _, _, _)).Times(0);
+
+  // Set the first response.
+  SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+  EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+  // Advance the URL index to 1 by faking an error.
+  ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+  payload_state.UpdateFailed(error);
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.UpdatesAbandonedEventCount", 1, _, _, _));
+
+  // Now, slightly change the response and set it again.
+  SetupPayloadStateWith2Urls("Hash8225", true, &payload_state, &response);
+  EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+  // Fake an error again.
+  payload_state.UpdateFailed(error);
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.UpdatesAbandonedEventCount", 2, _, _, _));
+
+  // Return a third different response.
+  SetupPayloadStateWith2Urls("Hash9999", true, &payload_state, &response);
+  EXPECT_EQ(3, payload_state.GetNumResponsesSeen());
+
+  // Make sure the url index was reset to 0 because of the new response.
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+  EXPECT_EQ(0,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(0,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+                 kDownloadSourceHttpsServer));
+  EXPECT_EQ(0,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+}
+
+TEST(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  int progress_bytes = 100;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+    .Times(AtLeast(2));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 2))
+    .Times(AtLeast(1));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+    .Times(AtLeast(2));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 2))
+    .Times(AtLeast(1));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(4));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(4));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(2));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+    .Times(AtLeast(7));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 1))
+    .Times(AtLeast(2));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 2))
+    .Times(AtLeast(1));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+    .Times(AtLeast(1));
+
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, progress_bytes))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kTotalBytesDownloadedFromHttp, progress_bytes))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+      .Times(AtLeast(1));
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  SetupPayloadStateWith2Urls("Hash5873", true, &payload_state, &response);
+  EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+  // This should advance the URL index.
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+  // This should advance the failure count only.
+  payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+  EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+  // This should advance the failure count only.
+  payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+  EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(2, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+  // This should advance the URL index as we've reached the
+  // max failure count and reset the failure count for the new URL index.
+  // This should also wrap around the URL index and thus cause the payload
+  // attempt number to be incremented.
+  payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(2, payload_state.GetUrlSwitchCount());
+  EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+  // This should advance the URL index.
+  payload_state.UpdateFailed(ErrorCode::kPayloadHashMismatchError);
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(3, payload_state.GetUrlSwitchCount());
+  EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+  // This should advance the URL index and payload attempt number due to
+  // wrap-around of URL index.
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMissingError);
+  EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+  EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+  // This HTTP error code should only increase the failure count.
+  payload_state.UpdateFailed(static_cast<ErrorCode>(
+      static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 404));
+  EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+  EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+  // And that failure count should be reset when we download some bytes
+  // afterwards.
+  payload_state.DownloadProgress(progress_bytes);
+  EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+  EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+  // Now, slightly change the response and set it again.
+  SetupPayloadStateWith2Urls("Hash8532", true, &payload_state, &response);
+  EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+  // Make sure the url index was reset to 0 because of the new response.
+  EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+  EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+    .Times(AtLeast(1));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+    .Times(AtLeast(1));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _))
+    .Times(AtLeast(2));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+    .Times(AtLeast(1));
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.PayloadAttemptNumber", 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.FullPayloadAttemptNumber", 1, _, _, _));
+
+  // This should just advance the payload attempt number;
+  EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  payload_state.DownloadComplete();
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) {
+  OmahaResponse response;
+  response.is_delta_payload = true;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+    .Times(AtLeast(1));
+
+  // kPrefsFullPayloadAttemptNumber is not incremented for delta payloads.
+  EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+    .Times(AtLeast(1));
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _))
+    .Times(1);
+
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+    .Times(AtLeast(1));
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+  // Metrics for Full payload attempt number is not sent with Delta payloads.
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.PayloadAttemptNumber", 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.FullPayloadAttemptNumber", _, _, _, _))
+      .Times(0);
+
+  // This should just advance the payload attempt number;
+  EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  payload_state.DownloadComplete();
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, SetResponseResetsInvalidUrlIndex) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+
+  // Generate enough events to advance URL index, failure count and
+  // payload attempt number all to 1.
+  payload_state.DownloadComplete();
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+  // Now, simulate a corrupted url index on persisted store which gets
+  // loaded when update_engine restarts. Using a different prefs object
+  // so as to not bother accounting for the uninteresting calls above.
+  FakeSystemState fake_system_state2;
+  NiceMock<MockPrefs>* prefs2 = fake_system_state2.mock_prefs();
+  EXPECT_CALL(*prefs2, Exists(_)).WillRepeatedly(Return(true));
+  EXPECT_CALL(*prefs2, GetInt64(_, _)).Times(AtLeast(1));
+  EXPECT_CALL(*prefs2, GetInt64(kPrefsPayloadAttemptNumber, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs2, GetInt64(kPrefsFullPayloadAttemptNumber, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlIndex, _))
+      .WillRepeatedly(DoAll(SetArgumentPointee<1>(2), Return(true)));
+  EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlFailureCount, _))
+    .Times(AtLeast(1));
+  EXPECT_CALL(*prefs2, GetInt64(kPrefsUrlSwitchCount, _))
+    .Times(AtLeast(1));
+
+  // Note: This will be a different payload object, but the response should
+  // have the same hash as before so as to not trivially reset because the
+  // response was different. We want to specifically test that even if the
+  // response is same, we should reset the state if we find it corrupted.
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state2));
+  SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+
+  // Make sure all counters get reset to 0 because of the corrupted URL index
+  // we supplied above.
+  EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NoBackoffInteractiveChecks) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  OmahaRequestParams params(&fake_system_state);
+  params.Init("", "", true);  // is_interactive = True.
+  fake_system_state.set_request_params(&params);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // Simulate two failures (enough to cause payload backoff) and check
+  // again that we're ready to re-download without any backoff as this is
+  // an interactive check.
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForP2PUpdates) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  OmahaRequestParams params(&fake_system_state);
+  params.Init("", "", false);  // is_interactive = False.
+  fake_system_state.set_request_params(&params);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // Simulate two failures (enough to cause payload backoff) and check
+  // again that we're ready to re-download without any backoff as this is
+  // an interactive check.
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+  // Set p2p url.
+  payload_state.SetUsingP2PForDownloading(true);
+  payload_state.SetP2PUrl("http://mypeer:52909/path/to/file");
+  // Should not backoff for p2p updates.
+  EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+  payload_state.SetP2PUrl("");
+  // No actual p2p update if no url is provided.
+  EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForDeltaPayloads) {
+  OmahaResponse response;
+  response.is_delta_payload = true;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // Simulate a successful download and see that we're ready to download
+  // again without any backoff as this is a delta payload.
+  payload_state.DownloadComplete();
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+  // Simulate two failures (enough to cause payload backoff) and check
+  // again that we're ready to re-download without any backoff as this is
+  // a delta payload.
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+static void CheckPayloadBackoffState(PayloadState* payload_state,
+                                     int expected_attempt_number,
+                                     TimeDelta expected_days) {
+  payload_state->DownloadComplete();
+  EXPECT_EQ(expected_attempt_number,
+      payload_state->GetFullPayloadAttemptNumber());
+  EXPECT_TRUE(payload_state->ShouldBackoffDownload());
+  Time backoff_expiry_time = payload_state->GetBackoffExpiryTime();
+  // Add 1 hour extra to the 6 hour fuzz check to tolerate edge cases.
+  TimeDelta max_fuzz_delta = TimeDelta::FromHours(7);
+  Time expected_min_time = Time::Now() + expected_days - max_fuzz_delta;
+  Time expected_max_time = Time::Now() + expected_days + max_fuzz_delta;
+  EXPECT_LT(expected_min_time.ToInternalValue(),
+            backoff_expiry_time.ToInternalValue());
+  EXPECT_GT(expected_max_time.ToInternalValue(),
+            backoff_expiry_time.ToInternalValue());
+}
+
+TEST(PayloadStateTest, BackoffPeriodsAreInCorrectRange) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+
+  CheckPayloadBackoffState(&payload_state, 1,  TimeDelta::FromDays(1));
+  CheckPayloadBackoffState(&payload_state, 2,  TimeDelta::FromDays(2));
+  CheckPayloadBackoffState(&payload_state, 3,  TimeDelta::FromDays(4));
+  CheckPayloadBackoffState(&payload_state, 4,  TimeDelta::FromDays(8));
+  CheckPayloadBackoffState(&payload_state, 5,  TimeDelta::FromDays(16));
+  CheckPayloadBackoffState(&payload_state, 6,  TimeDelta::FromDays(16));
+  CheckPayloadBackoffState(&payload_state, 7,  TimeDelta::FromDays(16));
+  CheckPayloadBackoffState(&payload_state, 8,  TimeDelta::FromDays(16));
+  CheckPayloadBackoffState(&payload_state, 9,  TimeDelta::FromDays(16));
+  CheckPayloadBackoffState(&payload_state, 10,  TimeDelta::FromDays(16));
+}
+
+TEST(PayloadStateTest, BackoffLogicCanBeDisabled) {
+  OmahaResponse response;
+  response.disable_payload_backoff = true;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+
+  // Simulate a successful download and see that we are ready to download
+  // again without any backoff.
+  payload_state.DownloadComplete();
+  EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+  // Test again, this time by simulating two errors that would cause
+  // the payload attempt number to increment due to wrap around. And
+  // check that we are still ready to re-download without any backoff.
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+  EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+  EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+  EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) {
+  OmahaResponse response;
+  response.disable_payload_backoff = true;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  int https_total = 0;
+  int http_total = 0;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+  EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+  // Simulate a previous attempt with in order to set an initial non-zero value
+  // for the total bytes downloaded for HTTP.
+  int prev_chunk = 323456789;
+  http_total += prev_chunk;
+  payload_state.DownloadProgress(prev_chunk);
+
+  // Ensure that the initial values for HTTP reflect this attempt.
+  EXPECT_EQ(prev_chunk,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(http_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+  // Change the response hash so as to simulate a new response which will
+  // reset the current bytes downloaded, but not the total bytes downloaded.
+  SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+  EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+  // First, simulate successful download of a few bytes over HTTP.
+  int first_chunk = 5000000;
+  http_total += first_chunk;
+  payload_state.DownloadProgress(first_chunk);
+  // Test that first all progress is made on HTTP and none on HTTPS.
+  EXPECT_EQ(first_chunk,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(http_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+                 kDownloadSourceHttpsServer));
+  EXPECT_EQ(https_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+  // Simulate an error that'll cause the url index to point to https.
+  ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+  payload_state.UpdateFailed(error);
+
+  // Test that no new progress is made on HTTP and new progress is on HTTPS.
+  int second_chunk = 23456789;
+  https_total += second_chunk;
+  payload_state.DownloadProgress(second_chunk);
+  EXPECT_EQ(first_chunk,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(http_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(second_chunk, payload_state.GetCurrentBytesDownloaded(
+              kDownloadSourceHttpsServer));
+  EXPECT_EQ(https_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+  // Simulate error to go back to http.
+  payload_state.UpdateFailed(error);
+  int third_chunk = 32345678;
+  int http_chunk = first_chunk + third_chunk;
+  http_total += third_chunk;
+  int https_chunk = second_chunk;
+  payload_state.DownloadProgress(third_chunk);
+
+  // Test that third chunk is again back on HTTP. HTTPS remains on second chunk.
+  EXPECT_EQ(http_chunk,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(http_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(second_chunk, payload_state.GetCurrentBytesDownloaded(
+                 kDownloadSourceHttpsServer));
+  EXPECT_EQ(https_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+  // Simulate error (will cause URL switch), set p2p is to be used and
+  // then do 42MB worth of progress
+  payload_state.UpdateFailed(error);
+  payload_state.SetUsingP2PForDownloading(true);
+  int p2p_total = 42 * 1000 * 1000;
+  payload_state.DownloadProgress(p2p_total);
+
+  EXPECT_EQ(p2p_total,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpPeer));
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.SuccessfulMBsDownloadedFromHttpServer",
+      http_chunk / kNumBytesInOneMiB, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.TotalMBsDownloadedFromHttpServer",
+      http_total / kNumBytesInOneMiB, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.SuccessfulMBsDownloadedFromHttpsServer",
+      https_chunk / kNumBytesInOneMiB, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.TotalMBsDownloadedFromHttpsServer",
+      https_total / kNumBytesInOneMiB, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.SuccessfulMBsDownloadedFromHttpPeer",
+      p2p_total / kNumBytesInOneMiB, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.TotalMBsDownloadedFromHttpPeer",
+      p2p_total / kNumBytesInOneMiB, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.UpdateURLSwitches",
+      3, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateUrlSwitchCount,
+      3, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.UpdateDurationMinutes",
+      _, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateTotalDurationMinutes,
+      _, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.UpdateDurationUptimeMinutes",
+      _, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.DownloadSourcesUsed",
+      (1 << kDownloadSourceHttpsServer) | (1 << kDownloadSourceHttpServer) |
+      (1 << kDownloadSourceHttpPeer),
+      _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.DownloadOverheadPercentage", 318, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage,
+      314, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      "Installer.PayloadFormat", kPayloadTypeFull, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeFull, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.AttemptsCount.Total", 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateAttemptCount, 1, _, _, _));
+
+  payload_state.UpdateSucceeded();
+
+  // Make sure the metrics are reset after a successful update.
+  EXPECT_EQ(0,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(0,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+                 kDownloadSourceHttpsServer));
+  EXPECT_EQ(0,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+  EXPECT_EQ(0, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, DownloadSourcesUsedIsCorrect) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+
+  // Simulate progress in order to mark HTTP as one of the sources used.
+  int num_bytes = 42 * 1000 * 1000;
+  payload_state.DownloadProgress(num_bytes);
+
+  // Check that this was done via HTTP.
+  EXPECT_EQ(num_bytes,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(num_bytes,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+  // Check that only HTTP is reported as a download source.
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.DownloadSourcesUsed",
+      (1 << kDownloadSourceHttpServer),
+      _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricSuccessfulUpdateDownloadSourcesUsed,
+      (1 << kDownloadSourceHttpServer),
+      _, _, _));
+
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RestartingUpdateResetsMetrics) {
+  OmahaResponse response;
+  FakeSystemState fake_system_state;
+  PayloadState payload_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // Set the first response.
+  SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+
+  int num_bytes = 10000;
+  payload_state.DownloadProgress(num_bytes);
+  EXPECT_EQ(num_bytes,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(num_bytes,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+                 kDownloadSourceHttpsServer));
+  EXPECT_EQ(0,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+  payload_state.UpdateRestarted();
+  // Make sure the current bytes downloaded is reset, but not the total bytes.
+  EXPECT_EQ(0,
+            payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+  EXPECT_EQ(num_bytes,
+            payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+}
+
+TEST(PayloadStateTest, NumRebootsIncrementsCorrectly) {
+  FakeSystemState fake_system_state;
+  PayloadState payload_state;
+
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AtLeast(0));
+  EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 1)).Times(AtLeast(1));
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  payload_state.UpdateRestarted();
+  EXPECT_EQ(0, payload_state.GetNumReboots());
+
+  fake_system_state.set_system_rebooted(true);
+  payload_state.UpdateResumed();
+  // Num reboots should be incremented because system rebooted detected.
+  EXPECT_EQ(1, payload_state.GetNumReboots());
+
+  fake_system_state.set_system_rebooted(false);
+  payload_state.UpdateResumed();
+  // Num reboots should now be 1 as reboot was not detected.
+  EXPECT_EQ(1, payload_state.GetNumReboots());
+
+  // Restart the update again to verify we set the num of reboots back to 0.
+  payload_state.UpdateRestarted();
+  EXPECT_EQ(0, payload_state.GetNumReboots());
+}
+
+TEST(PayloadStateTest, RollbackVersion) {
+  FakeSystemState fake_system_state;
+  PayloadState payload_state;
+
+  NiceMock<MockPrefs>* mock_powerwash_safe_prefs =
+      fake_system_state.mock_powerwash_safe_prefs();
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // Verify pre-conditions are good.
+  EXPECT_TRUE(payload_state.GetRollbackVersion().empty());
+
+  // Mock out the os version and make sure it's blacklisted correctly.
+  string rollback_version = "2345.0.0";
+  OmahaRequestParams params(&fake_system_state);
+  params.Init(rollback_version, "", false);
+  fake_system_state.set_request_params(&params);
+
+  EXPECT_CALL(*mock_powerwash_safe_prefs, SetString(kPrefsRollbackVersion,
+                                                    rollback_version));
+  payload_state.Rollback();
+
+  EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+  // Change it up a little and verify we load it correctly.
+  rollback_version = "2345.0.1";
+  // Let's verify we can reload it correctly.
+  EXPECT_CALL(*mock_powerwash_safe_prefs, GetString(
+      kPrefsRollbackVersion, _)).WillOnce(DoAll(
+          SetArgumentPointee<1>(rollback_version), Return(true)));
+  EXPECT_CALL(*mock_powerwash_safe_prefs, SetString(kPrefsRollbackVersion,
+                                                    rollback_version));
+  payload_state.LoadRollbackVersion();
+  EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+  // Check that we report only UpdateEngine.Rollback.* metrics in
+  // UpdateSucceeded().
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+              SendEnumToUMA(
+                  metrics::kMetricRollbackResult,
+                  static_cast<int>(metrics::RollbackResult::kSuccess),
+                  static_cast<int>(metrics::RollbackResult::kNumConstants)));
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, DurationsAreCorrect) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+
+  // Set the clock to a well-known time - 1 second on the wall-clock
+  // and 2 seconds on the monotonic clock
+  fake_clock.SetWallclockTime(Time::FromInternalValue(1000000));
+  fake_clock.SetMonotonicTime(Time::FromInternalValue(2000000));
+
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // Check that durations are correct for a successful update where
+  // time has advanced 7 seconds on the wall clock and 4 seconds on
+  // the monotonic clock.
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  fake_clock.SetWallclockTime(Time::FromInternalValue(8000000));
+  fake_clock.SetMonotonicTime(Time::FromInternalValue(6000000));
+  payload_state.UpdateSucceeded();
+  EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 7000000);
+  EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 4000000);
+
+  // Check that durations are reset when a new response comes in.
+  SetupPayloadStateWith2Urls("Hash8594", true, &payload_state, &response);
+  EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 0);
+  EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 0);
+
+  // Advance time a bit (10 secs), simulate download progress and
+  // check that durations are updated.
+  fake_clock.SetWallclockTime(Time::FromInternalValue(18000000));
+  fake_clock.SetMonotonicTime(Time::FromInternalValue(16000000));
+  payload_state.DownloadProgress(10);
+  EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 10000000);
+  EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 10000000);
+
+  // Now simulate a reboot by resetting monotonic time (to 5000) and
+  // creating a new PayloadState object and check that we load the
+  // durations correctly (e.g. they are the same as before).
+  fake_clock.SetMonotonicTime(Time::FromInternalValue(5000));
+  PayloadState payload_state2;
+  EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+  EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 10000000);
+  EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+            10000000);
+
+  // Advance wall-clock by 7 seconds and monotonic clock by 6 seconds
+  // and check that the durations are increased accordingly.
+  fake_clock.SetWallclockTime(Time::FromInternalValue(25000000));
+  fake_clock.SetMonotonicTime(Time::FromInternalValue(6005000));
+  payload_state2.UpdateSucceeded();
+  EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 17000000);
+  EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+            16000000);
+}
+
+TEST(PayloadStateTest, RebootAfterSuccessfulUpdateTest) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+
+  // Set the clock to a well-known time (t = 30 seconds).
+  fake_clock.SetWallclockTime(Time::FromInternalValue(
+      30 * Time::kMicrosecondsPerSecond));
+
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // Make the update succeed.
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+  payload_state.UpdateSucceeded();
+
+  // Check that the marker was written.
+  EXPECT_TRUE(fake_prefs.Exists(kPrefsSystemUpdatedMarker));
+
+  // Now simulate a reboot and set the wallclock time to a later point
+  // (t = 500 seconds). We do this by using a new PayloadState object
+  // and checking that it emits the right UMA metric with the right
+  // value.
+  fake_clock.SetWallclockTime(Time::FromInternalValue(
+      500 * Time::kMicrosecondsPerSecond));
+  PayloadState payload_state2;
+  EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+
+  // Expect 500 - 30 seconds = 470 seconds ~= 7 min 50 sec
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.TimeToRebootMinutes",
+      7, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricTimeToRebootMinutes,
+      7, _, _, _));
+  fake_system_state.set_system_rebooted(true);
+
+  payload_state2.UpdateEngineStarted();
+
+  // Check that the marker was nuked.
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsSystemUpdatedMarker));
+}
+
+TEST(PayloadStateTest, RestartAfterCrash) {
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // Only the |kPrefsAttemptInProgress| state variable should be read.
+  EXPECT_CALL(*prefs, Exists(_)).Times(0);
+  EXPECT_CALL(*prefs, SetString(_, _)).Times(0);
+  EXPECT_CALL(*prefs, SetInt64(_, _)).Times(0);
+  EXPECT_CALL(*prefs, SetBoolean(_, _)).Times(0);
+  EXPECT_CALL(*prefs, GetString(_, _)).Times(0);
+  EXPECT_CALL(*prefs, GetInt64(_, _)).Times(0);
+  EXPECT_CALL(*prefs, GetBoolean(_, _)).Times(0);
+  EXPECT_CALL(*prefs, GetBoolean(kPrefsAttemptInProgress, _));
+
+  // No metrics are reported after a crash.
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+              SendToUMA(_, _, _, _, _)).Times(0);
+
+  // Simulate an update_engine restart without a reboot.
+  fake_system_state.set_system_rebooted(false);
+
+  payload_state.UpdateEngineStarted();
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsNoReporting) {
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  // If there's no marker at startup, ensure we don't report a metric.
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+      SendEnumToUMA(
+          metrics::kMetricAttemptResult,
+          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+          _)).Times(0);
+  payload_state.UpdateEngineStarted();
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsReported) {
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakePrefs fake_prefs;
+
+  // If we have a marker at startup, ensure it's reported and the
+  // marker is then cleared.
+  fake_system_state.set_prefs(&fake_prefs);
+  fake_prefs.SetBoolean(kPrefsAttemptInProgress, true);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+      SendEnumToUMA(
+          metrics::kMetricAttemptResult,
+          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+          _)).Times(1);
+  payload_state.UpdateEngineStarted();
+
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsClearedOnSucceess) {
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakePrefs fake_prefs;
+
+  // Make sure the marker is written and cleared during an attempt and
+  // also that we DO NOT emit the metric (since the attempt didn't end
+  // abnormally).
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+      SendEnumToUMA(
+          metrics::kMetricAttemptResult,
+          static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+          _)).Times(0);
+
+  // Attempt not in progress, should be clear.
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+  payload_state.UpdateRestarted();
+
+  // Attempt not in progress, should be set.
+  EXPECT_TRUE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+  payload_state.UpdateSucceeded();
+
+  // Attempt not in progress, should be clear.
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(PayloadStateTest, CandidateUrlsComputedCorrectly) {
+  OmahaResponse response;
+  FakeSystemState fake_system_state;
+  PayloadState payload_state;
+
+  policy::MockDevicePolicy disable_http_policy;
+  fake_system_state.set_device_policy(&disable_http_policy);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // Test with no device policy. Should default to allowing http.
+  EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+      .WillRepeatedly(Return(false));
+
+  // Set the first response.
+  SetupPayloadStateWith2Urls("Hash8433", true, &payload_state, &response);
+
+  // Check that we use the HTTP URL since there is no value set for allowing
+  // http.
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+  // Test with device policy not allowing http updates.
+  EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+      .WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true)));
+
+  // Reset state and set again.
+  SetupPayloadStateWith2Urls("Hash8433", false, &payload_state, &response);
+
+  // Check that we skip the HTTP URL and use only the HTTPS url.
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+  // Advance the URL index to 1 by faking an error.
+  ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+  payload_state.UpdateFailed(error);
+
+  // Check that we still skip the HTTP URL and use only the HTTPS url.
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+
+  // Now, slightly change the response and set it again.
+  SetupPayloadStateWith2Urls("Hash2399", false, &payload_state, &response);
+
+  // Check that we still skip the HTTP URL and use only the HTTPS url.
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+  // Now, pretend that the HTTP policy is turned on. We want to make sure
+  // the new policy is honored.
+  policy::MockDevicePolicy enable_http_policy;
+  fake_system_state.set_device_policy(&enable_http_policy);
+  EXPECT_CALL(enable_http_policy, GetHttpDownloadsEnabled(_))
+      .WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true)));
+
+  // Now, set the same response using the same hash
+  // so that we can test that the state is reset not because of the
+  // hash but because of the policy change which results in candidate url
+  // list change.
+  SetupPayloadStateWith2Urls("Hash2399", true, &payload_state, &response);
+
+  // Check that we use the HTTP URL now and the failure count is reset.
+  EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+
+  // Fake a failure and see if we're moving over to the HTTPS url and update
+  // the URL switch count properly.
+  payload_state.UpdateFailed(error);
+  EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+  EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+  EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsDelta) {
+  OmahaResponse response;
+  response.is_delta_payload = true;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      "Installer.PayloadFormat", kPayloadTypeDelta, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeDelta, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+      kNumPayloadTypes));
+  payload_state.UpdateSucceeded();
+
+  // Mock the request to a request where the delta was disabled but Omaha sends
+  // a delta anyway and test again.
+  OmahaRequestParams params(&fake_system_state);
+  params.set_delta_okay(false);
+  fake_system_state.set_request_params(&params);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      "Installer.PayloadFormat", kPayloadTypeDelta, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeDelta,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+      kNumPayloadTypes));
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // Mock the request to a request where the delta was disabled.
+  OmahaRequestParams params(&fake_system_state);
+  params.set_delta_okay(false);
+  fake_system_state.set_request_params(&params);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      "Installer.PayloadFormat", kPayloadTypeForcedFull, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeForcedFull,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeForcedFull,
+      kNumPayloadTypes));
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) {
+  OmahaResponse response;
+  response.is_delta_payload = false;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+  // Mock the request to a request where the delta is enabled, although the
+  // result is full.
+  OmahaRequestParams params(&fake_system_state);
+  params.set_delta_okay(true);
+  fake_system_state.set_request_params(&params);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+    .Times(AnyNumber());
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      "Installer.PayloadFormat", kPayloadTypeFull, kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricAttemptPayloadType, kPayloadTypeFull,
+      kNumPayloadTypes));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+      metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+      kNumPayloadTypes));
+  payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateFailedMetric) {
+  FakeSystemState fake_system_state;
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakePrefs fake_prefs;
+  fake_system_state.set_prefs(&fake_prefs);
+
+  FakeHardware* fake_hardware = fake_system_state.fake_hardware();
+  fake_hardware->SetBootDevice("/dev/sda3");
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+  payload_state.UpdateSucceeded();
+  payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+  // Reboot into the same environment to get an UMA metric with a value of 1.
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.RebootToNewPartitionAttempt", 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, 1, _, _, _));
+  payload_state.ReportFailedBootIfNeeded();
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+  // Simulate a second update and reboot into the same environment, this should
+  // send a value of 2.
+  payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.RebootToNewPartitionAttempt", 2, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, 2, _, _, _));
+  payload_state.ReportFailedBootIfNeeded();
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+  // Simulate a third failed reboot to new version, but this time for a
+  // different payload. This should send a value of 1 this time.
+  payload_state.ExpectRebootInNewVersion("Version:3141592");
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.RebootToNewPartitionAttempt", 1, _, _, _));
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, 1, _, _, _));
+  payload_state.ReportFailedBootIfNeeded();
+  Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateSucceed) {
+  FakeSystemState fake_system_state;
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakePrefs fake_prefs;
+  fake_system_state.set_prefs(&fake_prefs);
+
+  FakeHardware* fake_hardware = fake_system_state.fake_hardware();
+  fake_hardware->SetBootDevice("/dev/sda3");
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+  payload_state.UpdateSucceeded();
+  payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+  // Change the BootDevice to a different one, no metric should be sent.
+  fake_hardware->SetBootDevice("/dev/sda5");
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.RebootToNewPartitionAttempt", _, _, _, _))
+      .Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, _, _, _, _))
+      .Times(0);
+  payload_state.ReportFailedBootIfNeeded();
+
+  // A second reboot in eiher partition should not send a metric.
+  payload_state.ReportFailedBootIfNeeded();
+  fake_hardware->SetBootDevice("/dev/sda3");
+  payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, RebootAfterCanceledUpdate) {
+  FakeSystemState fake_system_state;
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakePrefs fake_prefs;
+
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+  // Simulate a successful download and update.
+  payload_state.DownloadComplete();
+  payload_state.UpdateSucceeded();
+  payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.RebootToNewPartitionAttempt", _, _, _, _))
+      .Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, _, _, _, _))
+      .Times(0);
+
+  // Cancel the applied update.
+  payload_state.ResetUpdateStatus();
+
+  // Simulate a reboot.
+  payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs) {
+  FakeSystemState fake_system_state;
+  PayloadState payload_state;
+  FakePrefs fake_prefs;
+
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      "Installer.RebootToNewPartitionAttempt", _, _, _, _))
+      .Times(0);
+  EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+      metrics::kMetricFailedUpdateCount, _, _, _, _))
+      .Times(0);
+
+  // Simulate a reboot in this environment.
+  payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, DisallowP2PAfterTooManyAttempts) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakePrefs fake_prefs;
+  fake_system_state.set_prefs(&fake_prefs);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+  // Should allow exactly kMaxP2PAttempts...
+  for (int n = 0; n < kMaxP2PAttempts; n++) {
+    payload_state.P2PNewAttempt();
+    EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+  }
+  // ... but not more than that.
+  payload_state.P2PNewAttempt();
+  EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(PayloadStateTest, DisallowP2PAfterDeadline) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+  // Set the clock to 1 second.
+  Time epoch = Time::FromInternalValue(1000000);
+  fake_clock.SetWallclockTime(epoch);
+
+  // Do an attempt - this will set the timestamp.
+  payload_state.P2PNewAttempt();
+
+  // Check that the timestamp equals what we just set.
+  EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+  // Time hasn't advanced - this should work.
+  EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+  // Set clock to half the deadline - this should work.
+  fake_clock.SetWallclockTime(epoch +
+      TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds) / 2);
+  EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+  // Check that the first attempt timestamp hasn't changed just
+  // because the wall-clock time changed.
+  EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+  // Set clock to _just_ before the deadline - this should work.
+  fake_clock.SetWallclockTime(epoch +
+      TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds - 1));
+  EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+  // Set clock to _just_ after the deadline - this should not work.
+  fake_clock.SetWallclockTime(epoch +
+      TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds + 1));
+  EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(PayloadStateTest, P2PStateVarsInitialValue) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakePrefs fake_prefs;
+
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+  Time null_time = Time();
+  EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+  EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+}
+
+TEST(PayloadStateTest, P2PStateVarsArePersisted) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+  // Set the clock to something known.
+  Time time = Time::FromInternalValue(12345);
+  fake_clock.SetWallclockTime(time);
+
+  // New p2p attempt - as a side-effect this will update the p2p state vars.
+  payload_state.P2PNewAttempt();
+  EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+  EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+  // Now create a new PayloadState and check that it loads the state
+  // vars correctly.
+  PayloadState payload_state2;
+  EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+  EXPECT_EQ(1, payload_state2.GetP2PNumAttempts());
+  EXPECT_EQ(time, payload_state2.GetP2PFirstAttemptTimestamp());
+}
+
+TEST(PayloadStateTest, P2PStateVarsAreClearedOnNewResponse) {
+  OmahaResponse response;
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+  SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+  // Set the clock to something known.
+  Time time = Time::FromInternalValue(12345);
+  fake_clock.SetWallclockTime(time);
+
+  // New p2p attempt - as a side-effect this will update the p2p state vars.
+  payload_state.P2PNewAttempt();
+  EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+  EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+  // Set a new response...
+  SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+
+  // ... and check that it clears the P2P state vars.
+  Time null_time = Time();
+  EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+  EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_verifier.cc b/payload_verifier.cc
new file mode 100644
index 0000000..2b96186
--- /dev/null
+++ b/payload_verifier.cc
@@ -0,0 +1,219 @@
+// Copyright 2014 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_verifier.h"
+
+#include <base/logging.h>
+#include <openssl/pem.h>
+
+#include "update_engine/delta_performer.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+const uint32_t kSignatureMessageOriginalVersion = 1;
+const uint32_t kSignatureMessageCurrentVersion = 1;
+
+namespace {
+
+// The following is a standard PKCS1-v1_5 padding for SHA256 signatures, as
+// defined in RFC3447. It is prepended to the actual signature (32 bytes) to
+// form a sequence of 256 bytes (2048 bits) that is amenable to RSA signing. The
+// padded hash will look as follows:
+//
+//    0x00 0x01 0xff ... 0xff 0x00  ASN1HEADER  SHA256HASH
+//   |--------------205-----------||----19----||----32----|
+//
+// where ASN1HEADER is the ASN.1 description of the signed data. The complete 51
+// bytes of actual data (i.e. the ASN.1 header complete with the hash) are
+// packed as follows:
+//
+//  SEQUENCE(2+49) {
+//   SEQUENCE(2+13) {
+//    OBJECT(2+9) id-sha256
+//    NULL(2+0)
+//   }
+//   OCTET STRING(2+32) <actual signature bytes...>
+//  }
+const uint8_t kRSA2048SHA256Padding[] = {
+  // PKCS1-v1_5 padding
+  0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0x00,
+  // ASN.1 header
+  0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+  0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+  0x00, 0x04, 0x20,
+};
+
+}  // namespace
+
+bool PayloadVerifier::LoadPayload(const string& payload_path,
+                                  chromeos::Blob* out_payload,
+                                  DeltaArchiveManifest* out_manifest,
+                                  uint64_t* out_metadata_size) {
+  chromeos::Blob payload;
+  // Loads the payload and parses the manifest.
+  TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
+  LOG(INFO) << "Payload size: " << payload.size();
+  ErrorCode error = ErrorCode::kSuccess;
+  InstallPlan install_plan;
+  DeltaPerformer delta_performer(nullptr, nullptr, &install_plan);
+  TEST_AND_RETURN_FALSE(
+      delta_performer.ParsePayloadMetadata(payload, &error) ==
+      DeltaPerformer::kMetadataParseSuccess);
+  TEST_AND_RETURN_FALSE(delta_performer.GetManifest(out_manifest));
+  *out_metadata_size = delta_performer.GetMetadataSize();
+  LOG(INFO) << "Metadata size: " << *out_metadata_size;
+  out_payload->swap(payload);
+  return true;
+}
+
+bool PayloadVerifier::VerifySignature(const chromeos::Blob& signature_blob,
+                                      const string& public_key_path,
+                                      chromeos::Blob* out_hash_data) {
+  return VerifySignatureBlob(signature_blob, public_key_path,
+                             kSignatureMessageCurrentVersion, out_hash_data);
+}
+
+bool PayloadVerifier::VerifySignatureBlob(
+    const chromeos::Blob& signature_blob,
+    const string& public_key_path,
+    uint32_t client_version,
+    chromeos::Blob* out_hash_data) {
+  TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+  Signatures signatures;
+  LOG(INFO) << "signature size = " <<  signature_blob.size();
+  TEST_AND_RETURN_FALSE(signatures.ParseFromArray(signature_blob.data(),
+                                                  signature_blob.size()));
+
+  // Finds a signature that matches the current version.
+  int sig_index = 0;
+  for (; sig_index < signatures.signatures_size(); sig_index++) {
+    const Signatures_Signature& signature = signatures.signatures(sig_index);
+    if (signature.has_version() &&
+        signature.version() == client_version) {
+      break;
+    }
+  }
+  TEST_AND_RETURN_FALSE(sig_index < signatures.signatures_size());
+
+  const Signatures_Signature& signature = signatures.signatures(sig_index);
+  chromeos::Blob sig_data(signature.data().begin(), signature.data().end());
+
+  return GetRawHashFromSignature(sig_data, public_key_path, out_hash_data);
+}
+
+
+bool PayloadVerifier::GetRawHashFromSignature(
+    const chromeos::Blob& sig_data,
+    const string& public_key_path,
+    chromeos::Blob* out_hash_data) {
+  TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+  // The code below executes the equivalent of:
+  //
+  // openssl rsautl -verify -pubin -inkey |public_key_path|
+  //   -in |sig_data| -out |out_hash_data|
+
+  // Loads the public key.
+  FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
+  if (!fpubkey) {
+    LOG(ERROR) << "Unable to open public key file: " << public_key_path;
+    return false;
+  }
+
+  char dummy_password[] = { ' ', 0 };  // Ensure no password is read from stdin.
+  RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, nullptr, nullptr, dummy_password);
+  fclose(fpubkey);
+  TEST_AND_RETURN_FALSE(rsa != nullptr);
+  unsigned int keysize = RSA_size(rsa);
+  if (sig_data.size() > 2 * keysize) {
+    LOG(ERROR) << "Signature size is too big for public key size.";
+    RSA_free(rsa);
+    return false;
+  }
+
+  // Decrypts the signature.
+  chromeos::Blob hash_data(keysize);
+  int decrypt_size = RSA_public_decrypt(sig_data.size(),
+                                        sig_data.data(),
+                                        hash_data.data(),
+                                        rsa,
+                                        RSA_NO_PADDING);
+  RSA_free(rsa);
+  TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
+                        decrypt_size <= static_cast<int>(hash_data.size()));
+  hash_data.resize(decrypt_size);
+  out_hash_data->swap(hash_data);
+  return true;
+}
+
+bool PayloadVerifier::VerifySignedPayload(const string& payload_path,
+                                          const string& public_key_path,
+                                          uint32_t client_key_check_version) {
+  chromeos::Blob payload;
+  DeltaArchiveManifest manifest;
+  uint64_t metadata_size;
+  TEST_AND_RETURN_FALSE(LoadPayload(
+      payload_path, &payload, &manifest, &metadata_size));
+  TEST_AND_RETURN_FALSE(manifest.has_signatures_offset() &&
+                        manifest.has_signatures_size());
+  CHECK_EQ(payload.size(),
+           metadata_size + manifest.signatures_offset() +
+           manifest.signatures_size());
+  chromeos::Blob signature_blob(
+      payload.begin() + metadata_size + manifest.signatures_offset(),
+      payload.end());
+  chromeos::Blob signed_hash;
+  TEST_AND_RETURN_FALSE(VerifySignatureBlob(
+      signature_blob, public_key_path, client_key_check_version, &signed_hash));
+  TEST_AND_RETURN_FALSE(!signed_hash.empty());
+  chromeos::Blob hash;
+  TEST_AND_RETURN_FALSE(OmahaHashCalculator::RawHashOfBytes(
+      payload.data(), metadata_size + manifest.signatures_offset(), &hash));
+  PadRSA2048SHA256Hash(&hash);
+  TEST_AND_RETURN_FALSE(hash == signed_hash);
+  return true;
+}
+
+bool PayloadVerifier::PadRSA2048SHA256Hash(chromeos::Blob* hash) {
+  TEST_AND_RETURN_FALSE(hash->size() == 32);
+  hash->insert(hash->begin(),
+               reinterpret_cast<const char*>(kRSA2048SHA256Padding),
+               reinterpret_cast<const char*>(kRSA2048SHA256Padding +
+                                             sizeof(kRSA2048SHA256Padding)));
+  TEST_AND_RETURN_FALSE(hash->size() == 256);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_verifier.h b/payload_verifier.h
new file mode 100644
index 0000000..3f4f165
--- /dev/null
+++ b/payload_verifier.h
@@ -0,0 +1,79 @@
+// Copyright 2014 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_VERIFIER_H_
+#define UPDATE_ENGINE_PAYLOAD_VERIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signature verification.
+// See payload_generator/payload_signer.h for payload signing.
+
+namespace chromeos_update_engine {
+
+extern const uint32_t kSignatureMessageOriginalVersion;
+extern const uint32_t kSignatureMessageCurrentVersion;
+
+class PayloadVerifier {
+ public:
+  // Returns false if the payload signature can't be verified. Returns true
+  // otherwise and sets |out_hash| to the signed payload hash.
+  static bool VerifySignature(const chromeos::Blob& signature_blob,
+                              const std::string& public_key_path,
+                              chromeos::Blob* out_hash_data);
+
+  // Interprets signature_blob as a protocol buffer containing the Signatures
+  // message and decrypts the signature data using the public_key_path and
+  // stores the resultant raw hash data in out_hash_data. Returns true if
+  // everything is successful. False otherwise. It also takes the client_version
+  // and interprets the signature blob according to that version.
+  static bool VerifySignatureBlob(const chromeos::Blob& signature_blob,
+                                  const std::string& public_key_path,
+                                  uint32_t client_version,
+                                  chromeos::Blob* out_hash_data);
+
+  // Decrypts sig_data with the given public_key_path and populates
+  // out_hash_data with the decoded raw hash. Returns true if successful,
+  // false otherwise.
+  static bool GetRawHashFromSignature(const chromeos::Blob& sig_data,
+                                      const std::string& public_key_path,
+                                      chromeos::Blob* out_hash_data);
+
+  // Returns true if the payload in |payload_path| is signed and its hash can be
+  // verified using the public key in |public_key_path| with the signature
+  // of a given version in the signature blob. Returns false otherwise.
+  static bool VerifySignedPayload(const std::string& payload_path,
+                                  const std::string& public_key_path,
+                                  uint32_t client_key_check_version);
+
+  // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048
+  // using the PKCS#1 v1.5 scheme.
+  // hash should be a pointer to vector of exactly 256 bits. The vector
+  // will be modified in place and will result in having a length of
+  // 2048 bits. Returns true on success, false otherwise.
+  static bool PadRSA2048SHA256Hash(chromeos::Blob* hash);
+
+  // Reads the payload from the given |payload_path| into the |out_payload|
+  // vector. It also parses the manifest protobuf in the payload and returns it
+  // in |out_manifest| along with the size of the entire metadata in
+  // |out_metadata_size|.
+  static bool LoadPayload(const std::string& payload_path,
+                          chromeos::Blob* out_payload,
+                          DeltaArchiveManifest* out_manifest,
+                          uint64_t* out_metadata_size);
+
+ private:
+  // This should never be constructed
+  DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadVerifier);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_VERIFIER_H_
diff --git a/postinstall_runner_action.cc b/postinstall_runner_action.cc
new file mode 100644
index 0000000..f27aace
--- /dev/null
+++ b/postinstall_runner_action.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 2011 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/postinstall_runner_action.h"
+
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <vector>
+
+#include "update_engine/action_processor.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+namespace {
+// The absolute path to the post install command.
+const char kPostinstallScript[] = "/postinst";
+
+// Path to the binary file used by kPostinstallScript. Used to get and log the
+// file format of the binary to debug issues when the ELF format on the update
+// doesn't match the one on the current system. This path is not executed.
+const char kDebugPostinstallBinaryPath[] = "/usr/bin/cros_installer";
+}
+
+void PostinstallRunnerAction::PerformAction() {
+  CHECK(HasInputObject());
+  install_plan_ = GetInputObject();
+  const string install_device = install_plan_.install_path;
+  ScopedActionCompleter completer(processor_, this);
+
+  // Make mountpoint.
+  TEST_AND_RETURN(utils::MakeTempDirectory("/tmp/au_postint_mount.XXXXXX",
+                                           &temp_rootfs_dir_));
+  ScopedDirRemover temp_dir_remover(temp_rootfs_dir_);
+
+  const string mountable_device =
+      utils::MakePartitionNameForMount(install_device);
+  if (mountable_device.empty()) {
+    LOG(ERROR) << "Cannot make mountable device from " << install_device;
+    return;
+  }
+
+  if (!utils::MountFilesystem(mountable_device, temp_rootfs_dir_, MS_RDONLY))
+    return;
+
+  LOG(INFO) << "Performing postinst with install device " << install_device
+            << " and mountable device " << mountable_device;
+
+  temp_dir_remover.set_should_remove(false);
+  completer.set_should_complete(false);
+
+  if (install_plan_.powerwash_required) {
+    if (utils::CreatePowerwashMarkerFile(powerwash_marker_file_)) {
+      powerwash_marker_created_ = true;
+    } else {
+      completer.set_code(ErrorCode::kPostinstallPowerwashError);
+      return;
+    }
+  }
+
+  // Logs the file format of the postinstall script we are about to run. This
+  // will help debug when the postinstall script doesn't match the architecture
+  // of our build.
+  LOG(INFO) << "Format file for new " <<  kPostinstallScript << " is: "
+            << utils::GetFileFormat(temp_rootfs_dir_ + kPostinstallScript);
+  LOG(INFO) << "Format file for new " <<  kDebugPostinstallBinaryPath << " is: "
+            << utils::GetFileFormat(
+                temp_rootfs_dir_ + kDebugPostinstallBinaryPath);
+
+  // Runs the postinstall script asynchronously to free up the main loop while
+  // it's running.
+  vector<string> command;
+  if (!install_plan_.download_url.empty()) {
+    command.push_back(temp_rootfs_dir_ + kPostinstallScript);
+  } else {
+    // TODO(sosa): crbug.com/366207.
+    // If we're doing a rollback, just run our own postinstall.
+    command.push_back(kPostinstallScript);
+  }
+  command.push_back(install_device);
+  if (!Subprocess::Get().Exec(command, StaticCompletePostinstall, this)) {
+    CompletePostinstall(1);
+  }
+}
+
+void PostinstallRunnerAction::CompletePostinstall(int return_code) {
+  ScopedActionCompleter completer(processor_, this);
+  ScopedTempUnmounter temp_unmounter(temp_rootfs_dir_);
+  if (return_code != 0) {
+    LOG(ERROR) << "Postinst command failed with code: " << return_code;
+
+    // Undo any changes done to trigger Powerwash using clobber-state.
+    if (powerwash_marker_created_)
+      utils::DeletePowerwashMarkerFile(powerwash_marker_file_);
+
+    if (return_code == 3) {
+      // This special return code means that we tried to update firmware,
+      // but couldn't because we booted from FW B, and we need to reboot
+      // to get back to FW A.
+      completer.set_code(ErrorCode::kPostinstallBootedFromFirmwareB);
+    }
+
+    if (return_code == 4) {
+      // This special return code means that we tried to update firmware,
+      // but couldn't because we booted from FW B, and we need to reboot
+      // to get back to FW A.
+      completer.set_code(ErrorCode::kPostinstallFirmwareRONotUpdatable);
+    }
+
+    return;
+  }
+
+  LOG(INFO) << "Postinst command succeeded";
+  if (HasOutputPipe()) {
+    SetOutputObject(install_plan_);
+  }
+
+  completer.set_code(ErrorCode::kSuccess);
+}
+
+void PostinstallRunnerAction::StaticCompletePostinstall(int return_code,
+                                                        const string& output,
+                                                        void* p) {
+  reinterpret_cast<PostinstallRunnerAction*>(p)->CompletePostinstall(
+      return_code);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/postinstall_runner_action.h b/postinstall_runner_action.h
new file mode 100644
index 0000000..e7ac1c5
--- /dev/null
+++ b/postinstall_runner_action.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2010 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_POSTINSTALL_RUNNER_ACTION_H_
+#define UPDATE_ENGINE_POSTINSTALL_RUNNER_ACTION_H_
+
+#include <string>
+
+#include "update_engine/action.h"
+#include "update_engine/install_plan.h"
+
+// The Postinstall Runner Action is responsible for running the postinstall
+// script of a successfully downloaded update.
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerAction : public InstallPlanAction {
+ public:
+  PostinstallRunnerAction()
+      : powerwash_marker_created_(false),
+        powerwash_marker_file_(nullptr) {}
+
+  void PerformAction();
+
+  // Note that there's no support for terminating this action currently.
+  void TerminateProcessing() { CHECK(false); }
+
+  // Debugging/logging
+  static std::string StaticType() { return "PostinstallRunnerAction"; }
+  std::string Type() const { return StaticType(); }
+
+ private:
+  // Subprocess::Exec callback.
+  void CompletePostinstall(int return_code);
+  static void StaticCompletePostinstall(int return_code,
+                                        const std::string& output,
+                                        void* p);
+
+  InstallPlan install_plan_;
+  std::string temp_rootfs_dir_;
+
+  // True if Powerwash Marker was created before invoking post-install script.
+  // False otherwise. Used for cleaning up if post-install fails.
+  bool powerwash_marker_created_;
+
+  // Non-null value will cause post-install to override the default marker
+  // file name; used for testing.
+  const char* powerwash_marker_file_;
+
+  // Special ctor + friend declaration for testing purposes.
+  explicit PostinstallRunnerAction(const char* powerwash_marker_file)
+      : powerwash_marker_created_(false),
+        powerwash_marker_file_(powerwash_marker_file) {}
+
+  friend class PostinstallRunnerActionTest;
+
+  DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_POSTINSTALL_RUNNER_ACTION_H_
diff --git a/postinstall_runner_action_unittest.cc b/postinstall_runner_action_unittest.cc
new file mode 100644
index 0000000..4549272
--- /dev/null
+++ b/postinstall_runner_action_unittest.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 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/postinstall_runner_action.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos::MessageLoop;
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+  // DoTest with various combinations of do_losetup, err_code and
+  // powerwash_required.
+  void DoTest(bool do_losetup, int err_code, bool powerwash_required);
+
+ private:
+  static const char* kImageMountPointTemplate;
+
+  chromeos::GlibMessageLoop loop_;
+};
+
+class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  PostinstActionProcessorDelegate()
+      : code_(ErrorCode::kError),
+        code_set_(false) {}
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) {
+    MessageLoop::current()->BreakLoop();
+  }
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) {
+    if (action->Type() == PostinstallRunnerAction::StaticType()) {
+      code_ = code;
+      code_set_ = true;
+    }
+  }
+  ErrorCode code_;
+  bool code_set_;
+};
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
+  DoTest(true, 0, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
+  DoTest(true, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
+  DoTest(false, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
+  DoTest(true, 1, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
+  DoTest(true, 3, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareROErrScriptTest) {
+  DoTest(true, 4, false);
+}
+
+const char* PostinstallRunnerActionTest::kImageMountPointTemplate =
+    "au_destination-XXXXXX";
+
+void PostinstallRunnerActionTest::DoTest(
+    bool do_losetup,
+    int err_code,
+    bool powerwash_required) {
+  ASSERT_EQ(0, getuid()) << "Run me as root. Ideally don't run other tests "
+                         << "as root, tho.";
+  // True if the post-install action is expected to succeed.
+  bool should_succeed = do_losetup && !err_code;
+
+  string orig_cwd;
+  {
+    vector<char> buf(1000);
+    ASSERT_EQ(buf.data(), getcwd(buf.data(), buf.size()));
+    orig_cwd = string(buf.data(), strlen(buf.data()));
+  }
+
+  // Create a unique named working directory and chdir into it.
+  string cwd;
+  ASSERT_TRUE(utils::MakeTempDirectory(
+          "postinstall_runner_action_unittest-XXXXXX",
+          &cwd));
+  ASSERT_EQ(0, test_utils::Chdir(cwd));
+
+  // Create a 10MiB sparse file to be used as image; format it as ext2.
+  ASSERT_EQ(0, System(
+          "dd if=/dev/zero of=image.dat seek=10485759 bs=1 count=1 "
+          "status=none"));
+  ASSERT_EQ(0, System("mkfs.ext2 -F image.dat"));
+
+  // Create a uniquely named image mount point, mount the image.
+  ASSERT_EQ(0, System(string("mkdir -p ") + kStatefulPartition));
+  string mountpoint;
+  ASSERT_TRUE(utils::MakeTempDirectory(
+          string(kStatefulPartition) + "/" + kImageMountPointTemplate,
+          &mountpoint));
+  ASSERT_EQ(0, System(string("mount -o loop image.dat ") + mountpoint));
+
+  // Generate a fake postinst script inside the image.
+  string script = (err_code ?
+                   base::StringPrintf("#!/bin/bash\nexit %d", err_code) :
+                   base::StringPrintf(
+                       "#!/bin/bash\n"
+                       "mount | grep au_postint_mount | grep ext2\n"
+                       "if [ $? -eq 0 ]; then\n"
+                       "  touch %s/postinst_called\n"
+                       "fi\n",
+                       cwd.c_str()));
+  const string script_file_name = mountpoint + "/postinst";
+  ASSERT_TRUE(WriteFileString(script_file_name, script));
+  ASSERT_EQ(0, System(string("chmod a+x ") + script_file_name));
+
+  // Unmount image; do not remove the uniquely named directory as it will be
+  // reused during the test.
+  ASSERT_TRUE(utils::UnmountFilesystem(mountpoint));
+
+  // get a loop device we can use for the install device
+  string dev = "/dev/null";
+
+  unique_ptr<test_utils::ScopedLoopbackDeviceBinder> loop_releaser;
+  if (do_losetup) {
+    loop_releaser.reset(new test_utils::ScopedLoopbackDeviceBinder(
+            cwd + "/image.dat", &dev));
+  }
+
+  // We use a test-specific powerwash marker file, to avoid race conditions.
+  string powerwash_marker_file = mountpoint + "/factory_install_reset";
+  LOG(INFO) << ">>> powerwash_marker_file=" << powerwash_marker_file;
+
+  ActionProcessor processor;
+  ObjectFeederAction<InstallPlan> feeder_action;
+  InstallPlan install_plan;
+  install_plan.install_path = dev;
+  install_plan.download_url = "http://devserver:8080/update";
+  install_plan.powerwash_required = powerwash_required;
+  feeder_action.set_obj(install_plan);
+  PostinstallRunnerAction runner_action(powerwash_marker_file.c_str());
+  BondActions(&feeder_action, &runner_action);
+  ObjectCollectorAction<InstallPlan> collector_action;
+  BondActions(&runner_action, &collector_action);
+  PostinstActionProcessorDelegate delegate;
+  processor.EnqueueAction(&feeder_action);
+  processor.EnqueueAction(&runner_action);
+  processor.EnqueueAction(&collector_action);
+  processor.set_delegate(&delegate);
+
+  loop_.PostTask(FROM_HERE,
+                 base::Bind([&processor] { processor.StartProcessing(); }));
+  loop_.Run();
+  ASSERT_FALSE(processor.IsRunning());
+
+  EXPECT_TRUE(delegate.code_set_);
+  EXPECT_EQ(should_succeed, delegate.code_ == ErrorCode::kSuccess);
+  EXPECT_EQ(should_succeed, !collector_action.object().install_path.empty());
+  if (should_succeed)
+    EXPECT_TRUE(install_plan == collector_action.object());
+
+  const base::FilePath kPowerwashMarkerPath(powerwash_marker_file);
+  string actual_cmd;
+  if (should_succeed && powerwash_required) {
+    EXPECT_TRUE(base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+    EXPECT_EQ(kPowerwashCommand, actual_cmd);
+  } else {
+    EXPECT_FALSE(
+        base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+  }
+
+  if (err_code == 2)
+    EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB, delegate.code_);
+
+  struct stat stbuf;
+  int rc = lstat((string(cwd) + "/postinst_called").c_str(), &stbuf);
+  if (should_succeed)
+    ASSERT_EQ(0, rc);
+  else
+    ASSERT_LT(rc, 0);
+
+  if (do_losetup) {
+    loop_releaser.reset(nullptr);
+  }
+
+  // Remove unique stateful directory.
+  ASSERT_EQ(0, System(string("rm -fr ") + mountpoint));
+
+  // Remove the temporary work directory.
+  ASSERT_EQ(0, test_utils::Chdir(orig_cwd));
+  ASSERT_EQ(0, System(string("rm -fr ") + cwd));
+}
+
+// Death tests don't seem to be working on Hardy
+TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) {
+  ASSERT_EQ(0, getuid());
+  PostinstallRunnerAction runner_action;
+  ASSERT_DEATH({ runner_action.TerminateProcessing(); },
+               "postinstall_runner_action.h:.*] Check failed");
+}
+
+}  // namespace chromeos_update_engine
diff --git a/prefs.cc b/prefs.cc
new file mode 100644
index 0000000..5b91939
--- /dev/null
+++ b/prefs.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 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/prefs.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+bool Prefs::Init(const base::FilePath& prefs_dir) {
+  prefs_dir_ = prefs_dir;
+  return true;
+}
+
+bool Prefs::GetString(const string& key, string* value) {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  if (!base::ReadFileToString(filename, value)) {
+    LOG(INFO) << key << " not present in " << prefs_dir_.value();
+    return false;
+  }
+  return true;
+}
+
+bool Prefs::SetString(const string& key, const string& value) {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  TEST_AND_RETURN_FALSE(base::CreateDirectory(filename.DirName()));
+  TEST_AND_RETURN_FALSE(base::WriteFile(filename, value.data(), value.size()) ==
+                        static_cast<int>(value.size()));
+  return true;
+}
+
+bool Prefs::GetInt64(const string& key, int64_t* value) {
+  string str_value;
+  if (!GetString(key, &str_value))
+    return false;
+  base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+  TEST_AND_RETURN_FALSE(base::StringToInt64(str_value, value));
+  return true;
+}
+
+bool Prefs::SetInt64(const string& key, const int64_t value) {
+  return SetString(key, base::Int64ToString(value));
+}
+
+bool Prefs::GetBoolean(const string& key, bool* value) {
+  string str_value;
+  if (!GetString(key, &str_value))
+    return false;
+  base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+  if (str_value == "false") {
+    *value = false;
+    return true;
+  }
+  if (str_value == "true") {
+    *value = true;
+    return true;
+  }
+  return false;
+}
+
+bool Prefs::SetBoolean(const string& key, const bool value) {
+  return SetString(key, value ? "true" : "false");
+}
+
+bool Prefs::Exists(const string& key) {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  return base::PathExists(filename);
+}
+
+bool Prefs::Delete(const string& key) {
+  base::FilePath filename;
+  TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+  return base::DeleteFile(filename, false);
+}
+
+bool Prefs::GetFileNameForKey(const string& key,
+                              base::FilePath* filename) {
+  // Allows only non-empty keys containing [A-Za-z0-9_-].
+  TEST_AND_RETURN_FALSE(!key.empty());
+  for (size_t i = 0; i < key.size(); ++i) {
+    char c = key.at(i);
+    TEST_AND_RETURN_FALSE(IsAsciiAlpha(c) || IsAsciiDigit(c) ||
+                          c == '_' || c == '-');
+  }
+  *filename = prefs_dir_.Append(key);
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/prefs.h b/prefs.h
new file mode 100644
index 0000000..1c4ec4f
--- /dev/null
+++ b/prefs.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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_PREFS_H_
+#define UPDATE_ENGINE_PREFS_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include "gtest/gtest_prod.h"  // for FRIEND_TEST
+#include "update_engine/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a preference store by storing the value associated with
+// a key in a separate file named after the key under a preference
+// store directory.
+
+class Prefs : public PrefsInterface {
+ public:
+  Prefs() {}
+
+  // Initializes the store by associating this object with |prefs_dir|
+  // as the preference store directory. Returns true on success, false
+  // otherwise.
+  bool Init(const base::FilePath& prefs_dir);
+
+  // PrefsInterface methods.
+  bool GetString(const std::string& key, std::string* value) override;
+  bool SetString(const std::string& key, const std::string& value) override;
+  bool GetInt64(const std::string& key, int64_t* value) override;
+  bool SetInt64(const std::string& key, const int64_t value) override;
+  bool GetBoolean(const std::string& key, bool* value) override;
+  bool SetBoolean(const std::string& key, const bool value) override;
+
+  bool Exists(const std::string& key) override;
+  bool Delete(const std::string& key) override;
+
+ private:
+  FRIEND_TEST(PrefsTest, GetFileNameForKey);
+  FRIEND_TEST(PrefsTest, GetFileNameForKeyBadCharacter);
+  FRIEND_TEST(PrefsTest, GetFileNameForKeyEmpty);
+
+  // Sets |filename| to the full path to the file containing the data
+  // associated with |key|. Returns true on success, false otherwise.
+  bool GetFileNameForKey(const std::string& key, base::FilePath* filename);
+
+  // Preference store directory.
+  base::FilePath prefs_dir_;
+
+  DISALLOW_COPY_AND_ASSIGN(Prefs);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PREFS_H_
diff --git a/prefs_interface.h b/prefs_interface.h
new file mode 100644
index 0000000..cdc6efe
--- /dev/null
+++ b/prefs_interface.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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_PREFS_INTERFACE_H_
+#define UPDATE_ENGINE_PREFS_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+// The prefs interface allows access to a persistent preferences
+// store. The two reasons for providing this as an interface are
+// testing as well as easier switching to a new implementation in the
+// future, if necessary.
+
+class PrefsInterface {
+ public:
+  virtual ~PrefsInterface() {}
+
+  // Gets a string |value| associated with |key|. Returns true on
+  // success, false on failure (including when the |key| is not
+  // present in the store).
+  virtual bool GetString(const std::string& key, std::string* value) = 0;
+
+  // Associates |key| with a string |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetString(const std::string& key, const std::string& value) = 0;
+
+  // Gets an int64_t |value| associated with |key|. Returns true on
+  // success, false on failure (including when the |key| is not
+  // present in the store).
+  virtual bool GetInt64(const std::string& key, int64_t* value) = 0;
+
+  // Associates |key| with an int64_t |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetInt64(const std::string& key, const int64_t value) = 0;
+
+  // Gets a boolean |value| associated with |key|. Returns true on
+  // success, false on failure (including when the |key| is not
+  // present in the store).
+  virtual bool GetBoolean(const std::string& key, bool* value) = 0;
+
+  // Associates |key| with a boolean |value|. Returns true on success,
+  // false otherwise.
+  virtual bool SetBoolean(const std::string& key, const bool value) = 0;
+
+  // Returns true if the setting exists (i.e. a file with the given key
+  // exists in the prefs directory)
+  virtual bool Exists(const std::string& key) = 0;
+
+  // Returns true if successfully deleted the file corresponding to
+  // this key. Calling with non-existent keys does nothing.
+  virtual bool Delete(const std::string& key) = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PREFS_INTERFACE_H_
diff --git a/prefs_unittest.cc b/prefs_unittest.cc
new file mode 100644
index 0000000..fd36acc
--- /dev/null
+++ b/prefs_unittest.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 2012 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/prefs.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+class PrefsTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(base::CreateNewTempDirectory("auprefs", &prefs_dir_));
+    ASSERT_TRUE(prefs_.Init(prefs_dir_));
+  }
+
+  void TearDown() override {
+    base::DeleteFile(prefs_dir_, true);  // recursive
+  }
+
+  bool SetValue(const string& key, const string& value) {
+    return base::WriteFile(prefs_dir_.Append(key), value.data(),
+                           value.length()) == static_cast<int>(value.length());
+  }
+
+  base::FilePath prefs_dir_;
+  Prefs prefs_;
+};
+
+TEST_F(PrefsTest, GetFileNameForKey) {
+  const char kKey[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-";
+  base::FilePath path;
+  EXPECT_TRUE(prefs_.GetFileNameForKey(kKey, &path));
+  EXPECT_EQ(prefs_dir_.Append(kKey).value(), path.value());
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyBadCharacter) {
+  base::FilePath path;
+  EXPECT_FALSE(prefs_.GetFileNameForKey("ABC abc", &path));
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyEmpty) {
+  base::FilePath path;
+  EXPECT_FALSE(prefs_.GetFileNameForKey("", &path));
+}
+
+TEST_F(PrefsTest, GetString) {
+  const char kKey[] = "test-key";
+  const string test_data = "test data";
+  ASSERT_TRUE(SetValue(kKey, test_data));
+  string value;
+  EXPECT_TRUE(prefs_.GetString(kKey, &value));
+  EXPECT_EQ(test_data, value);
+}
+
+TEST_F(PrefsTest, GetStringBadKey) {
+  string value;
+  EXPECT_FALSE(prefs_.GetString(",bad", &value));
+}
+
+TEST_F(PrefsTest, GetStringNonExistentKey) {
+  string value;
+  EXPECT_FALSE(prefs_.GetString("non-existent-key", &value));
+}
+
+TEST_F(PrefsTest, SetString) {
+  const char kKey[] = "my_test_key";
+  const char kValue[] = "some test value\non 2 lines";
+  EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringBadKey) {
+  const char kKey[] = ".no-dots";
+  EXPECT_FALSE(prefs_.SetString(kKey, "some value"));
+  EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, SetStringCreateDir) {
+  const char kKey[] = "a-test-key";
+  const char kValue[] = "test value";
+  base::FilePath subdir = prefs_dir_.Append("subdir1").Append("subdir2");
+  EXPECT_TRUE(prefs_.Init(subdir));
+  EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(subdir.Append(kKey), &value));
+  EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringDirCreationFailure) {
+  EXPECT_TRUE(prefs_.Init(base::FilePath("/dev/null")));
+  const char kKey[] = "test-key";
+  EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+}
+
+TEST_F(PrefsTest, SetStringFileCreationFailure) {
+  const char kKey[] = "a-test-key";
+  base::CreateDirectory(prefs_dir_.Append(kKey));
+  EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+  EXPECT_TRUE(base::DirectoryExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, GetInt64) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, " \n 25 \t "));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(25, value);
+}
+
+TEST_F(PrefsTest, GetInt64BadValue) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, "30a"));
+  int64_t value;
+  EXPECT_FALSE(prefs_.GetInt64(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetInt64Max) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, base::StringPrintf("%" PRIi64, kint64max)));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(kint64max, value);
+}
+
+TEST_F(PrefsTest, GetInt64Min) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, base::StringPrintf("%" PRIi64, kint64min)));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(kint64min, value);
+}
+
+TEST_F(PrefsTest, GetInt64Negative) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, " \t -100 \n "));
+  int64_t value;
+  EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+  EXPECT_EQ(-100, value);
+}
+
+TEST_F(PrefsTest, GetInt64NonExistentKey) {
+  int64_t value;
+  EXPECT_FALSE(prefs_.GetInt64("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetInt64) {
+  const char kKey[] = "test_int";
+  EXPECT_TRUE(prefs_.SetInt64(kKey, -123));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ("-123", value);
+}
+
+TEST_F(PrefsTest, SetInt64BadKey) {
+  const char kKey[] = "s p a c e s";
+  EXPECT_FALSE(prefs_.SetInt64(kKey, 20));
+  EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, SetInt64Max) {
+  const char kKey[] = "test-max-int";
+  EXPECT_TRUE(prefs_.SetInt64(kKey, kint64max));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ(base::StringPrintf("%" PRIi64, kint64max), value);
+}
+
+TEST_F(PrefsTest, SetInt64Min) {
+  const char kKey[] = "test-min-int";
+  EXPECT_TRUE(prefs_.SetInt64(kKey, kint64min));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ(base::StringPrintf("%" PRIi64, kint64min), value);
+}
+
+TEST_F(PrefsTest, GetBooleanFalse) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, " \n false \t "));
+  bool value;
+  EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+  EXPECT_FALSE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanTrue) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, " \t true \n "));
+  bool value;
+  EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+  EXPECT_TRUE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanBadValue) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, "1"));
+  bool value;
+  EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanBadEmptyValue) {
+  const char kKey[] = "test-key";
+  ASSERT_TRUE(SetValue(kKey, ""));
+  bool value;
+  EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanNonExistentKey) {
+  bool value;
+  EXPECT_FALSE(prefs_.GetBoolean("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetBooleanTrue) {
+  const char kKey[] = "test-bool";
+  EXPECT_TRUE(prefs_.SetBoolean(kKey, true));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ("true", value);
+}
+
+TEST_F(PrefsTest, SetBooleanFalse) {
+  const char kKey[] = "test-bool";
+  EXPECT_TRUE(prefs_.SetBoolean(kKey, false));
+  string value;
+  EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+  EXPECT_EQ("false", value);
+}
+
+TEST_F(PrefsTest, SetBooleanBadKey) {
+  const char kKey[] = "s p a c e s";
+  EXPECT_FALSE(prefs_.SetBoolean(kKey, true));
+  EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, ExistsWorks) {
+  const char kKey[] = "exists-key";
+
+  // test that the key doesn't exist before we set it.
+  EXPECT_FALSE(prefs_.Exists(kKey));
+
+  // test that the key exists after we set it.
+  ASSERT_TRUE(prefs_.SetInt64(kKey, 8));
+  EXPECT_TRUE(prefs_.Exists(kKey));
+}
+
+TEST_F(PrefsTest, DeleteWorks) {
+  const char kKey[] = "delete-key";
+
+  // test that it's alright to delete a non-existent key.
+  EXPECT_TRUE(prefs_.Delete(kKey));
+
+  // delete the key after we set it.
+  ASSERT_TRUE(prefs_.SetInt64(kKey, 0));
+  EXPECT_TRUE(prefs_.Delete(kKey));
+
+  // make sure it doesn't exist anymore.
+  EXPECT_FALSE(prefs_.Exists(kKey));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/proxy_resolver.cc b/proxy_resolver.cc
new file mode 100644
index 0000000..8b8a9c8
--- /dev/null
+++ b/proxy_resolver.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2010 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/proxy_resolver.h"
+
+#include <base/bind.h>
+#include <base/location.h>
+
+using chromeos::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char kNoProxy[] = "direct://";
+
+DirectProxyResolver::~DirectProxyResolver() {
+  if (idle_callback_id_ != MessageLoop::kTaskIdNull) {
+    // The DirectProxyResolver is instantiated as part of the UpdateAttempter
+    // which is also instantiated by default by the FakeSystemState, even when
+    // it is not used. We check the manage_shares_id_ before calling the
+    // MessageLoop::current() since the unit test using a FakeSystemState may
+    // have not define a MessageLoop for the current thread.
+    MessageLoop::current()->CancelTask(idle_callback_id_);
+    idle_callback_id_ = MessageLoop::kTaskIdNull;
+  }
+}
+
+bool DirectProxyResolver::GetProxiesForUrl(const string& url,
+                                           ProxiesResolvedFn callback,
+                                           void* data) {
+  idle_callback_id_ = MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(
+            &DirectProxyResolver::ReturnCallback,
+            base::Unretained(this),
+            callback,
+            data));
+  return true;
+}
+
+void DirectProxyResolver::ReturnCallback(ProxiesResolvedFn callback,
+                                         void* data) {
+  idle_callback_id_ = MessageLoop::kTaskIdNull;
+
+  // Initialize proxy pool with as many proxies as indicated (all identical).
+  deque<string> proxies(num_proxies_, kNoProxy);
+
+  (*callback)(proxies, data);
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/proxy_resolver.h b/proxy_resolver.h
new file mode 100644
index 0000000..9d6b73a
--- /dev/null
+++ b/proxy_resolver.h
@@ -0,0 +1,76 @@
+// Copyright (c) 2010 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_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <string>
+
+#include <base/logging.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+extern const char kNoProxy[];
+
+// Callback for a call to GetProxiesForUrl().
+// Resultant proxies are in |out_proxy|. Each will be in one of the
+// following forms:
+// http://<host>[:<port>] - HTTP proxy
+// socks{4,5}://<host>[:<port>] - SOCKS4/5 proxy
+// kNoProxy - no proxy
+typedef void (*ProxiesResolvedFn)(const std::deque<std::string>& proxies,
+                                  void* data);
+
+class ProxyResolver {
+ public:
+  ProxyResolver() {}
+  virtual ~ProxyResolver() {}
+
+  // Finds proxies for the given URL and returns them via the callback.
+  // |data| will be passed to the callback.
+  // Returns true on success.
+  virtual bool GetProxiesForUrl(const std::string& url,
+                                ProxiesResolvedFn callback,
+                                void* data) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
+};
+
+// Always says to not use a proxy
+class DirectProxyResolver : public ProxyResolver {
+ public:
+  DirectProxyResolver() = default;
+  ~DirectProxyResolver() override;
+  bool GetProxiesForUrl(const std::string& url,
+                        ProxiesResolvedFn callback,
+                        void* data) override;
+
+  // Set the number of direct (non-) proxies to be returned by resolver.
+  // The default value is 1; higher numbers are currently used in testing.
+  inline void set_num_proxies(size_t num_proxies) {
+    num_proxies_ = num_proxies;
+  }
+
+ private:
+  // The ID of the main loop callback.
+  chromeos::MessageLoop::TaskId idle_callback_id_{
+      chromeos::MessageLoop::kTaskIdNull};
+
+  // Number of direct proxies to return on resolved list; currently used for
+  // testing.
+  size_t num_proxies_{1};
+
+  // The MainLoop callback, from here we return to the client.
+  void ReturnCallback(ProxiesResolvedFn callback, void* data);
+  DISALLOW_COPY_AND_ASSIGN(DirectProxyResolver);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PROXY_RESOLVER_H_
diff --git a/real_dbus_wrapper.h b/real_dbus_wrapper.h
new file mode 100644
index 0000000..0c6847b
--- /dev/null
+++ b/real_dbus_wrapper.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2010 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_REAL_DBUS_WRAPPER_H_
+#define UPDATE_ENGINE_REAL_DBUS_WRAPPER_H_
+
+// A mockable interface for DBus.
+
+#include <base/macros.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+
+namespace chromeos_update_engine {
+
+class RealDBusWrapper : public DBusWrapperInterface {
+  DBusGProxy* ProxyNewForName(DBusGConnection* connection,
+                              const char* name,
+                              const char* path,
+                              const char* interface) override {
+    return dbus_g_proxy_new_for_name(connection,
+                                     name,
+                                     path,
+                                     interface);
+  }
+
+  void ProxyUnref(DBusGProxy* proxy) override {
+    g_object_unref(proxy);
+  }
+
+  DBusGConnection* BusGet(DBusBusType type, GError** error) override {
+    return dbus_g_bus_get(type, error);
+  }
+
+  gboolean ProxyCall_0_1(DBusGProxy* proxy,
+                         const char* method,
+                         GError** error,
+                         GHashTable** out1) override {
+    return dbus_g_proxy_call(proxy, method, error, G_TYPE_INVALID,
+                             dbus_g_type_get_map("GHashTable",
+                                                 G_TYPE_STRING,
+                                                 G_TYPE_VALUE),
+                             out1, G_TYPE_INVALID);
+  }
+
+  gboolean ProxyCall_0_1(DBusGProxy* proxy,
+                         const char* method,
+                         GError** error,
+                         gint* out1) override {
+    return dbus_g_proxy_call(proxy, method, error, G_TYPE_INVALID,
+                             G_TYPE_INT, out1, G_TYPE_INVALID);
+  }
+
+  gboolean ProxyCall_1_0(DBusGProxy* proxy,
+                         const char* method,
+                         GError** error,
+                         gint in1) override {
+    return dbus_g_proxy_call(proxy, method, error,
+                             G_TYPE_INT, in1,
+                             G_TYPE_INVALID, G_TYPE_INVALID);
+  }
+
+  gboolean ProxyCall_3_0(DBusGProxy* proxy,
+                         const char* method,
+                         GError** error,
+                         const char* in1,
+                         const char* in2,
+                         const char* in3) override {
+    return dbus_g_proxy_call(
+        proxy, method, error,
+        G_TYPE_STRING, in1, G_TYPE_STRING, in2, G_TYPE_STRING, in3,
+        G_TYPE_INVALID, G_TYPE_INVALID);
+  }
+
+  void ProxyAddSignal_1(DBusGProxy* proxy,
+                        const char* signal_name,
+                        GType type1) override {
+    dbus_g_object_register_marshaller(
+        g_cclosure_marshal_generic, G_TYPE_NONE, type1, G_TYPE_INVALID);
+    dbus_g_proxy_add_signal(proxy, signal_name, type1, G_TYPE_INVALID);
+  }
+
+  void ProxyAddSignal_2(DBusGProxy* proxy,
+                        const char* signal_name,
+                        GType type1,
+                        GType type2) override {
+    dbus_g_object_register_marshaller(
+        g_cclosure_marshal_generic, G_TYPE_NONE, type1, type2, G_TYPE_INVALID);
+    dbus_g_proxy_add_signal(proxy, signal_name, type1, type2, G_TYPE_INVALID);
+  }
+
+  void ProxyConnectSignal(DBusGProxy* proxy,
+                          const char* signal_name,
+                          GCallback handler,
+                          void* data,
+                          GClosureNotify free_data_func) override {
+    dbus_g_proxy_connect_signal(proxy, signal_name, handler, data,
+                                free_data_func);
+  }
+
+  void ProxyDisconnectSignal(DBusGProxy* proxy,
+                             const char* signal_name,
+                             GCallback handler,
+                             void* data) override {
+    dbus_g_proxy_disconnect_signal(proxy, signal_name, handler, data);
+  }
+
+  DBusConnection* ConnectionGetConnection(DBusGConnection* gbus) override {
+    return dbus_g_connection_get_connection(gbus);
+  }
+
+  void DBusBusAddMatch(DBusConnection* connection,
+                       const char* rule,
+                       DBusError* error) override {
+    dbus_bus_add_match(connection, rule, error);
+  }
+
+  dbus_bool_t DBusConnectionAddFilter(
+      DBusConnection* connection,
+      DBusHandleMessageFunction function,
+      void* user_data,
+      DBusFreeFunction free_data_function) override {
+    return dbus_connection_add_filter(connection,
+                                      function,
+                                      user_data,
+                                      free_data_function);
+  }
+
+  void DBusConnectionRemoveFilter(DBusConnection* connection,
+                                  DBusHandleMessageFunction function,
+                                  void* user_data) override {
+    dbus_connection_remove_filter(connection, function, user_data);
+  }
+
+  dbus_bool_t DBusMessageIsSignal(DBusMessage* message,
+                                  const char* interface,
+                                  const char* signal_name) override {
+    return dbus_message_is_signal(message, interface, signal_name);
+  }
+
+  dbus_bool_t DBusMessageGetArgs_3(DBusMessage* message,
+                                   DBusError* error,
+                                   char** out1,
+                                   char** out2,
+                                   char** out3) override {
+    return dbus_message_get_args(message, error,
+                                 DBUS_TYPE_STRING, out1,
+                                 DBUS_TYPE_STRING, out2,
+                                 DBUS_TYPE_STRING, out3,
+                                 G_TYPE_INVALID);
+  }
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_REAL_DBUS_WRAPPER_H_
diff --git a/real_system_state.cc b/real_system_state.cc
new file mode 100644
index 0000000..f16f2f8
--- /dev/null
+++ b/real_system_state.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 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/real_system_state.h"
+
+#include <base/files/file_util.h>
+#include <base/time/time.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/update_manager/state_factory.h"
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+RealSystemState::RealSystemState()
+    : device_policy_(nullptr),
+      connection_manager_(this),
+      update_attempter_(this, &dbus_),
+      request_params_(this),
+      system_rebooted_(false) {}
+
+bool RealSystemState::Initialize() {
+  metrics_lib_.Init();
+
+  if (!prefs_.Init(base::FilePath(kPrefsDirectory))) {
+    LOG(ERROR) << "Failed to initialize preferences.";
+    return false;
+  }
+
+  if (!powerwash_safe_prefs_.Init(base::FilePath(kPowerwashSafePrefsDir))) {
+    LOG(ERROR) << "Failed to initialize powerwash preferences.";
+    return false;
+  }
+
+  if (!utils::FileExists(kSystemRebootedMarkerFile)) {
+    if (!utils::WriteFile(kSystemRebootedMarkerFile, "", 0)) {
+      LOG(ERROR) << "Could not create reboot marker file";
+      return false;
+    }
+    system_rebooted_ = true;
+  }
+
+  // Initialize the Update Manager using the default state factory.
+  chromeos_update_manager::State* um_state =
+      chromeos_update_manager::DefaultStateFactory(
+          &policy_provider_, &dbus_, this);
+  if (!um_state) {
+    LOG(ERROR) << "Failed to initialize the Update Manager.";
+    return false;
+  }
+  update_manager_.reset(
+      new chromeos_update_manager::UpdateManager(
+          &clock_, base::TimeDelta::FromSeconds(5),
+          base::TimeDelta::FromHours(12), um_state));
+
+  // The P2P Manager depends on the Update Manager for its initialization.
+  p2p_manager_.reset(P2PManager::Construct(
+          nullptr, &clock_, update_manager_.get(), "cros_au",
+          kMaxP2PFilesToKeep, base::TimeDelta::FromDays(kMaxP2PFileAgeDays)));
+
+  if (!payload_state_.Initialize(this)) {
+    LOG(ERROR) << "Failed to initialize the payload state object.";
+    return false;
+  }
+
+  // Initialize the update attempter.
+  update_attempter_.Init();
+
+  // All is well. Initialization successful.
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/real_system_state.h b/real_system_state.h
new file mode 100644
index 0000000..7e0c16d
--- /dev/null
+++ b/real_system_state.h
@@ -0,0 +1,136 @@
+// Copyright (c) 2013 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_REAL_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
+
+#include "update_engine/system_state.h"
+
+#include <memory>
+
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/clock.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/hardware.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state.h"
+#include "update_engine/prefs.h"
+#include "update_engine/real_dbus_wrapper.h"
+#include "update_engine/update_attempter.h"
+#include "update_engine/update_manager/update_manager.h"
+
+namespace chromeos_update_engine {
+
+// A real implementation of the SystemStateInterface which is
+// used by the actual product code.
+class RealSystemState : public SystemState {
+ public:
+  // Constructs all system objects that do not require separate initialization;
+  // see Initialize() below for the remaining ones.
+  RealSystemState();
+
+  // Initializes and sets systems objects that require an initialization
+  // separately from construction. Returns |true| on success.
+  bool Initialize();
+
+  inline void set_device_policy(
+      const policy::DevicePolicy* device_policy) override {
+    device_policy_ = device_policy;
+  }
+
+  inline const policy::DevicePolicy* device_policy() override {
+    return device_policy_;
+  }
+
+  inline ClockInterface* clock() override { return &clock_; }
+
+  inline ConnectionManager* connection_manager() override {
+    return &connection_manager_;
+  }
+
+  inline HardwareInterface* hardware() override { return &hardware_; }
+
+  inline MetricsLibraryInterface* metrics_lib() override {
+    return &metrics_lib_;
+  }
+
+  inline PrefsInterface* prefs() override { return &prefs_; }
+
+  inline PrefsInterface* powerwash_safe_prefs() override {
+      return &powerwash_safe_prefs_;
+    }
+
+  inline PayloadStateInterface* payload_state() override {
+    return &payload_state_;
+  }
+
+  inline UpdateAttempter* update_attempter() override {
+    return &update_attempter_;
+  }
+
+  inline OmahaRequestParams* request_params() override {
+    return &request_params_;
+  }
+
+  inline P2PManager* p2p_manager() override { return p2p_manager_.get(); }
+
+  inline chromeos_update_manager::UpdateManager* update_manager() override {
+    return update_manager_.get();
+  }
+
+  inline bool system_rebooted() override { return system_rebooted_; }
+
+ private:
+  // Interface for the clock.
+  Clock clock_;
+
+  // The latest device policy object from the policy provider.
+  const policy::DevicePolicy* device_policy_;
+
+  // The connection manager object that makes download
+  // decisions depending on the current type of connection.
+  ConnectionManager connection_manager_;
+
+  // Interface for the hardware functions.
+  Hardware hardware_;
+
+  // The Metrics Library interface for reporting UMA stats.
+  MetricsLibrary metrics_lib_;
+
+  // Interface for persisted store.
+  Prefs prefs_;
+
+  // Interface for persisted store that persists across powerwashes.
+  Prefs powerwash_safe_prefs_;
+
+  // All state pertaining to payload state such as
+  // response, URL, backoff states.
+  PayloadState payload_state_;
+
+  // The dbus object used to initialize the update attempter.
+  RealDBusWrapper dbus_;
+
+  // Pointer to the update attempter object.
+  UpdateAttempter update_attempter_;
+
+  // Common parameters for all Omaha requests.
+  OmahaRequestParams request_params_;
+
+  std::unique_ptr<P2PManager> p2p_manager_;
+
+  std::unique_ptr<chromeos_update_manager::UpdateManager> update_manager_;
+
+  policy::PolicyProvider policy_provider_;
+
+  // If true, this is the first instance of the update engine since the system
+  // rebooted. Important for tracking whether you are running instance of the
+  // update engine on first boot or due to a crash/restart.
+  bool system_rebooted_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
diff --git a/run_unittests b/run_unittests
new file mode 100755
index 0000000..7a7c85a
--- /dev/null
+++ b/run_unittests
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# Copyright (c) 2012 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.
+
+# Runs the update engine unit tests, including both userland and run-as-root
+# tests.
+
+if [ ! -e ./update_engine_unittests ]; then
+  echo 'Error: unit test binary missing' >&2
+  exit 1
+fi
+
+user_pass=0
+./update_engine_unittests --gtest_filter='-*.RunAsRoot*' && user_pass=1
+root_pass=0
+sudo ./update_engine_unittests --gtest_filter='*.RunAsRoot*' && root_pass=1
+
+echo -n "User tests: " && [ $user_pass == 1 ] && echo "PASSED" || echo "FAILED"
+echo -n "Root tests: " && [ $root_pass == 1 ] && echo "PASSED" || echo "FAILED"
+
+exit $((2 - user_pass - root_pass))
diff --git a/sample_images/disk_ext2_1k.txt b/sample_images/disk_ext2_1k.txt
new file mode 100644
index 0000000..1494535
--- /dev/null
+++ b/sample_images/disk_ext2_1k.txt
@@ -0,0 +1 @@
+default 16777216 1024
diff --git a/sample_images/disk_ext2_4k.txt b/sample_images/disk_ext2_4k.txt
new file mode 100644
index 0000000..cac718f
--- /dev/null
+++ b/sample_images/disk_ext2_4k.txt
@@ -0,0 +1 @@
+default 16777216 4096
diff --git a/sample_images/disk_ext2_ue_settings.txt b/sample_images/disk_ext2_ue_settings.txt
new file mode 100644
index 0000000..554f7da
--- /dev/null
+++ b/sample_images/disk_ext2_ue_settings.txt
@@ -0,0 +1 @@
+ue_settings 16777216 4096
diff --git a/sample_images/generate_image.sh b/sample_images/generate_image.sh
new file mode 100755
index 0000000..c4f132a
--- /dev/null
+++ b/sample_images/generate_image.sh
@@ -0,0 +1,158 @@
+#!/bin/bash
+
+# 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.
+
+set -e
+
+# cleanup <path>
+# Unmount and remove the mountpoint <path>
+cleanup() {
+  if ! sudo umount "$1" 2>/dev/null; then
+    if mountpoint -q "$1"; then
+      sync && sudo umount "$1"
+    fi
+  fi
+  rmdir "$1"
+}
+
+# add_files_default <mntdir> <block_size>
+# Add several test files to the image mounted in <mntdir>.
+add_files_default() {
+  local mntdir="$1"
+  local block_size="$2"
+
+  ### Generate the files used in unittest with descriptive names.
+  sudo touch "${mntdir}"/empty-file
+
+  # regular: Regular files.
+  echo "small file" | sudo dd of="${mntdir}"/regular-small status=none
+  dd if=/dev/zero bs=1024 count=16 status=none | tr '\0' '\141' |
+    sudo dd of="${mntdir}"/regular-16k status=none
+  sudo dd if=/dev/zero of="${mntdir}"/regular-32k-zeros bs=1024 count=16 \
+    status=none
+
+  echo "with net_cap" | sudo dd of="${mntdir}"/regular-with_net_cap status=none
+  sudo setcap cap_net_raw=ep "${mntdir}"/regular-with_net_cap
+
+  # sparse_empty: Files with no data blocks at all (only sparse holes).
+  sudo truncate --size=10240 "${mntdir}"/sparse_empty-10k
+  sudo truncate --size=$(( block_size * 2 )) "${mntdir}"/sparse_empty-2blocks
+
+  # sparse: Files with some data blocks but also sparse holes.
+  echo -n "foo" |
+    sudo dd of="${mntdir}"/sparse-16k-last_block bs=1 \
+      seek=$(( 16 * 1024 - 3)) status=none
+
+  # ext2 inodes have 12 direct blocks, one indirect, one double indirect and
+  # one triple indirect. 10000 should be enough to have an indirect and double
+  # indirect block.
+  echo -n "foo" |
+    sudo dd of="${mntdir}"/sparse-10000blocks bs=1 \
+      seek=$(( block_size * 10000 )) status=none
+
+  sudo truncate --size=16384 "${mntdir}"/sparse-16k-first_block
+  echo "first block" | sudo dd of="${mntdir}"/sparse-16k-first_block status=none
+
+  sudo truncate --size=16384 "${mntdir}"/sparse-16k-holes
+  echo "a" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=100 status=none
+  echo "b" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=10000 status=none
+
+  # link: symlinks and hardlinks.
+  sudo ln -s "broken-link" "${mntdir}"/link-short_symlink
+  sudo ln -s $(dd if=/dev/zero bs=256 count=1 status=none | tr '\0' '\141') \
+    "${mntdir}"/link-long_symlink
+  sudo ln "${mntdir}"/regular-16k "${mntdir}"/link-hard-regular-16k
+
+  # Directories.
+  sudo mkdir -p "${mntdir}"/dir1/dir2/dir1
+  echo "foo" | sudo tee "${mntdir}"/dir1/dir2/file >/dev/null
+  echo "bar" | sudo tee "${mntdir}"/dir1/file >/dev/null
+
+  # removed: removed files that should not be listed.
+  echo "We will remove this file so it's contents will be somewhere in the " \
+    "empty space data but it won't be all zeros." |
+    sudo dd of="${mntdir}"/removed conv=fsync status=none
+  sudo rm "${mntdir}"/removed
+}
+
+# add_files_ue_settings <mntdir> <block_size>
+# Add the update_engine.conf settings file. This file contains the
+add_files_ue_settings() {
+  local mntdir="$1"
+
+  sudo mkdir -p "${mntdir}"/etc >/dev/null
+  sudo tee "${mntdir}"/etc/update_engine.conf >/dev/null <<EOF
+PAYLOAD_MINOR_VERSION=1234
+EOF
+  # Example of a real lsb-release file released on link stable.
+  sudo tee "${mntdir}"/etc/lsb-release >/dev/null <<EOF
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+CHROMEOS_BOARD_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_CANARY_APPID={90F229CE-83E2-4FAF-8479-E368A34938B1}
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_RELEASE_BOARD=link-signed-mp-v4keys
+CHROMEOS_RELEASE_BRANCH_NUMBER=63
+CHROMEOS_RELEASE_BUILD_NUMBER=6946
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_CHROME_MILESTONE=43
+CHROMEOS_RELEASE_DESCRIPTION=6946.63.0 (Official Build) stable-channel link
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=stable-channel
+CHROMEOS_RELEASE_VERSION=6946.63.0
+GOOGLE_RELEASE=6946.63.0
+EOF
+}
+
+# generate_fs <filename> <kind> <size> [block_size] [block_groups]
+generate_fs() {
+  local filename="$1"
+  local kind="$2"
+  local size="$3"
+  local block_size="${4:-4096}"
+  local block_groups="${5:-}"
+
+  local mkfs_opts=( -q -F -b "${block_size}" -L "ROOT-TEST" -t ext2 )
+  if [[ -n "${block_groups}" ]]; then
+    mkfs_opts+=( -G "${block_groups}" )
+  fi
+
+  local mntdir=$(mktemp --tmpdir -d generate_ext2.XXXXXX)
+  trap 'cleanup "${mntdir}"; rm -f "${filename}"' INT TERM EXIT
+
+  # Cleanup old image.
+  if [[ -e "${filename}" ]]; then
+    rm -f "${filename}"
+  fi
+  truncate --size="${size}" "${filename}"
+
+  mkfs.ext2 "${mkfs_opts[@]}" "${filename}"
+  sudo mount "${filename}" "${mntdir}" -o loop
+
+  case "${kind}" in
+    ue_settings)
+      add_files_ue_settings "${mntdir}" "${block_size}"
+      ;;
+    default)
+      add_files_default "${mntdir}" "${block_size}"
+      ;;
+  esac
+
+  cleanup "${mntdir}"
+  trap - INT TERM EXIT
+}
+
+image_desc="${1:-}"
+output_dir="${2:-}"
+
+if [[ ! -e "${image_desc}" || ! -d "${output_dir}" ]]; then
+  echo "Use: $0 <image_description.txt> <output_dir>" >&2
+  exit 1
+fi
+
+args=( $(cat ${image_desc}) )
+dest_image="${output_dir}/$(basename ${image_desc} .txt).img"
+generate_fs "${dest_image}" "${args[@]}"
diff --git a/sample_omaha_v3_response.xml b/sample_omaha_v3_response.xml
new file mode 100644
index 0000000..abba523
--- /dev/null
+++ b/sample_omaha_v3_response.xml
@@ -0,0 +1,19 @@
+<response protocol="3.0" server="prod">
+  <daystart elapsed_seconds="56652"/>
+  <app appid="{90f229ce-83e2-4faf-8479-e368a34938b1}" status="ok">
+    <updatecheck status="ok">
+      <urls>
+        <url codebase="https://storage.googleapis.com/chromeos-releases-public/canary-channel/canary-channel/3095.0.0/"/>
+      </urls>
+      <manifest version="3095.0.0">
+        <packages>
+          <package hash="HVOmp67vBjPdvpWmOC2Uw4UDwsc=" name="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed" required="true" size="400752559"/>
+        </packages>
+        <actions>
+          <action event="update" run="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed"/>
+          <action ChromeOSVersion="3095.0.0" ChromeVersion="24.0.1307.0" IsDelta="true" IsDeltaPayload="false" MaxDaysToScatter="14" MetadataSignatureRsa="xXrO/LahHlKk3YmqEf1qE0PN587Sc2IJV+FN7J7x1h49waNQIy/QwYO4LaOySgETe5JZXtkAEzzqakfJwxQ2pVfzj1GkExwjd5LTn1He2GvA73B8fKbS4bfP7dbUFwD5039xCwf1U2gezFViOiOPiVURx/pEsdhv+Cqx/3HbjIuj5au2dooSyDxLC5AnODzAKyYfAcjMuiLON+9SqmctJW+VjzdY9SbJAnkH2qqVjFyBKAXsYT+hOTIJ3MJpg8OSVxMMtGB99PxbOJ52F37d2Y5Fws/AUkNnNEsan/WRJA1kuWoS6rpeR8JQYuVhLiK2u/KpOcvMVRw3Q2VUxtcAGw==" MetadataSize="58315" event="postinstall" sha256="DIAVxoI+8NpsudUawOA5U92VHlaxQBS3ejN4EPM6T2A="/>
+        </actions>
+      </manifest>
+    </updatecheck>
+  </app>
+</response>
diff --git a/subprocess.cc b/subprocess.cc
new file mode 100644
index 0000000..6a34641
--- /dev/null
+++ b/subprocess.cc
@@ -0,0 +1,275 @@
+// Copyright (c) 2012 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/subprocess.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/glib_utils.h"
+
+using chromeos::MessageLoop;
+using std::shared_ptr;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void Subprocess::GChildExitedCallback(GPid pid, gint status, gpointer data) {
+  SubprocessRecord* record = reinterpret_cast<SubprocessRecord*>(data);
+
+  // Make sure we read any remaining process output and then close the pipe.
+  OnStdoutReady(record);
+
+  MessageLoop::current()->CancelTask(record->task_id);
+  record->task_id = MessageLoop::kTaskIdNull;
+  if (IGNORE_EINTR(close(record->stdout_fd)) != 0) {
+    PLOG(ERROR) << "Error closing fd " << record->stdout_fd;
+  }
+  g_spawn_close_pid(pid);
+  gint use_status = status;
+  if (WIFEXITED(status))
+    use_status = WEXITSTATUS(status);
+
+  if (status) {
+    LOG(INFO) << "Subprocess status: " << use_status;
+  }
+  if (!record->stdout.empty()) {
+    LOG(INFO) << "Subprocess output:\n" << record->stdout;
+  }
+  if (record->callback) {
+    record->callback(use_status, record->stdout, record->callback_data);
+  }
+  Get().subprocess_records_.erase(record->tag);
+}
+
+void Subprocess::GRedirectStderrToStdout(gpointer user_data) {
+  dup2(1, 2);
+}
+
+void Subprocess::OnStdoutReady(SubprocessRecord* record) {
+  char buf[1024];
+  ssize_t rc = 0;
+  do {
+    rc = HANDLE_EINTR(read(record->stdout_fd, buf, arraysize(buf)));
+    if (rc < 0) {
+      // EAGAIN and EWOULDBLOCK are normal return values when there's no more
+      // input as we are in non-blocking mode.
+      if (errno != EWOULDBLOCK && errno != EAGAIN) {
+        PLOG(ERROR) << "Error reading fd " << record->stdout_fd;
+      }
+    } else {
+      record->stdout.append(buf, rc);
+    }
+  } while (rc > 0);
+}
+
+namespace {
+void FreeArgv(char** argv) {
+  for (int i = 0; argv[i]; i++) {
+    free(argv[i]);
+    argv[i] = nullptr;
+  }
+}
+
+void FreeArgvInError(char** argv) {
+  FreeArgv(argv);
+  LOG(ERROR) << "Ran out of memory copying args.";
+}
+
+// Note: Caller responsible for free()ing the returned value!
+// Will return null on failure and free any allocated memory.
+char** ArgPointer() {
+  const char* keys[] = {"LD_LIBRARY_PATH", "PATH"};
+  char** ret = new char*[arraysize(keys) + 1];
+  int pointer = 0;
+  for (size_t i = 0; i < arraysize(keys); i++) {
+    if (getenv(keys[i])) {
+      ret[pointer] = strdup(base::StringPrintf("%s=%s", keys[i],
+                                               getenv(keys[i])).c_str());
+      if (!ret[pointer]) {
+        FreeArgv(ret);
+        delete [] ret;
+        return nullptr;
+      }
+      ++pointer;
+    }
+  }
+  ret[pointer] = nullptr;
+  return ret;
+}
+
+class ScopedFreeArgPointer {
+ public:
+  explicit ScopedFreeArgPointer(char** arr) : arr_(arr) {}
+  ~ScopedFreeArgPointer() {
+    if (!arr_)
+      return;
+    for (int i = 0; arr_[i]; i++)
+      free(arr_[i]);
+    delete[] arr_;
+  }
+ private:
+  char** arr_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedFreeArgPointer);
+};
+}  // namespace
+
+uint32_t Subprocess::Exec(const vector<string>& cmd,
+                          ExecCallback callback,
+                          void* p) {
+  return ExecFlags(cmd, static_cast<GSpawnFlags>(0), true, callback, p);
+}
+
+uint32_t Subprocess::ExecFlags(const vector<string>& cmd,
+                               GSpawnFlags flags,
+                               bool redirect_stderr_to_stdout,
+                               ExecCallback callback,
+                               void* p) {
+  unique_ptr<gchar*, utils::GLibStrvFreeDeleter> argv(
+       utils::StringVectorToGStrv(cmd));
+
+  char** argp = ArgPointer();
+  if (!argp) {
+    FreeArgvInError(argv.get());  // null in argv[i] terminates argv.
+    return 0;
+  }
+  ScopedFreeArgPointer argp_free(argp);
+
+  shared_ptr<SubprocessRecord> record(new SubprocessRecord);
+  record->callback = callback;
+  record->callback_data = p;
+  gint stdout_fd = -1;
+  GError* error = nullptr;
+  bool success = g_spawn_async_with_pipes(
+      nullptr,  // working directory
+      argv.get(),
+      argp,
+      static_cast<GSpawnFlags>(flags | G_SPAWN_DO_NOT_REAP_CHILD),  // flags
+      // child setup function:
+      redirect_stderr_to_stdout ? GRedirectStderrToStdout : nullptr,
+      nullptr,  // child setup data pointer
+      &record->pid,
+      nullptr,
+      &stdout_fd,
+      nullptr,
+      &error);
+  if (!success) {
+    LOG(ERROR) << "g_spawn_async failed: " << utils::GetAndFreeGError(&error);
+    return 0;
+  }
+  record->tag =
+      g_child_watch_add(record->pid, GChildExitedCallback, record.get());
+  record->stdout_fd = stdout_fd;
+  subprocess_records_[record->tag] = record;
+
+  // Capture the subprocess output. Make our end of the pipe non-blocking.
+  int fd_flags = fcntl(stdout_fd, F_GETFL, 0) | O_NONBLOCK;
+  if (HANDLE_EINTR(fcntl(record->stdout_fd, F_SETFL, fd_flags)) < 0) {
+    LOG(ERROR) << "Unable to set non-blocking I/O mode on fd "
+               << record->stdout_fd << ".";
+  }
+
+  record->task_id = MessageLoop::current()->WatchFileDescriptor(
+      FROM_HERE,
+      record->stdout_fd,
+      MessageLoop::WatchMode::kWatchRead,
+      true,
+      base::Bind(&Subprocess::OnStdoutReady, record.get()));
+
+  return record->tag;
+}
+
+void Subprocess::KillExec(uint32_t tag) {
+  const auto& record = subprocess_records_.find(tag);
+  if (record == subprocess_records_.end())
+    return;
+  record->second->callback = nullptr;
+  kill(record->second->pid, SIGTERM);
+}
+
+bool Subprocess::SynchronousExecFlags(const vector<string>& cmd,
+                                      GSpawnFlags flags,
+                                      int* return_code,
+                                      string* stdout) {
+  if (stdout) {
+    *stdout = "";
+  }
+  GError* err = nullptr;
+  unique_ptr<char*[]> argv(new char*[cmd.size() + 1]);
+  for (unsigned int i = 0; i < cmd.size(); i++) {
+    argv[i] = strdup(cmd[i].c_str());
+    if (!argv[i]) {
+      FreeArgvInError(argv.get());  // null in argv[i] terminates argv.
+      return false;
+    }
+  }
+  argv[cmd.size()] = nullptr;
+
+  char** argp = ArgPointer();
+  if (!argp) {
+    FreeArgvInError(argv.get());  // null in argv[i] terminates argv.
+    return false;
+  }
+  ScopedFreeArgPointer argp_free(argp);
+
+  char* child_stdout;
+  bool success = g_spawn_sync(
+      nullptr,  // working directory
+      argv.get(),
+      argp,
+      static_cast<GSpawnFlags>(G_SPAWN_STDERR_TO_DEV_NULL |
+                               G_SPAWN_SEARCH_PATH | flags),  // flags
+      GRedirectStderrToStdout,  // child setup function
+      nullptr,  // data for child setup function
+      &child_stdout,
+      nullptr,
+      return_code,
+      &err);
+  FreeArgv(argv.get());
+  LOG_IF(INFO, err) << utils::GetAndFreeGError(&err);
+  if (child_stdout) {
+    if (stdout) {
+      *stdout = child_stdout;
+    } else if (*child_stdout) {
+      LOG(INFO) << "Subprocess output:\n" << child_stdout;
+    }
+    g_free(child_stdout);
+  }
+  return success;
+}
+
+bool Subprocess::SynchronousExec(const vector<string>& cmd,
+                                 int* return_code,
+                                 string* stdout) {
+  return SynchronousExecFlags(cmd,
+                              static_cast<GSpawnFlags>(0),
+                              return_code,
+                              stdout);
+}
+
+bool Subprocess::SubprocessInFlight() {
+  for (const auto& tag_record_pair : subprocess_records_) {
+    if (tag_record_pair.second->callback)
+      return true;
+  }
+  return false;
+}
+
+Subprocess* Subprocess::subprocess_singleton_ = nullptr;
+
+}  // namespace chromeos_update_engine
diff --git a/subprocess.h b/subprocess.h
new file mode 100644
index 0000000..13f7eb7
--- /dev/null
+++ b/subprocess.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2011 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_SUBPROCESS_H_
+#define UPDATE_ENGINE_SUBPROCESS_H_
+
+#include <glib.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+// The Subprocess class is a singleton. It's used to spawn off a subprocess
+// and get notified when the subprocess exits. The result of Exec() can
+// be saved and used to cancel the callback request and kill your process. If
+// you know you won't call KillExec(), you may safely lose the return value
+// from Exec().
+
+namespace chromeos_update_engine {
+
+class Subprocess {
+ public:
+  typedef void(*ExecCallback)(int return_code,
+                              const std::string& output,
+                              void *p);
+
+  static void Init() {
+    CHECK(!subprocess_singleton_);
+    subprocess_singleton_ = new Subprocess;
+  }
+
+  // Returns a tag > 0 on success.
+  uint32_t Exec(const std::vector<std::string>& cmd,
+                ExecCallback callback,
+                void* p);
+  uint32_t ExecFlags(const std::vector<std::string>& cmd,
+                     GSpawnFlags flags,
+                     bool redirect_stderr_to_stdout,
+                     ExecCallback callback,
+                     void* p);
+
+  // Kills the running process with SIGTERM and ignores the callback.
+  void KillExec(uint32_t tag);
+
+  // Executes a command synchronously. Returns true on success. If |stdout| is
+  // non-null, the process output is stored in it, otherwise the output is
+  // logged. Note that stderr is redirected to stdout.
+  static bool SynchronousExecFlags(const std::vector<std::string>& cmd,
+                                   GSpawnFlags flags,
+                                   int* return_code,
+                                   std::string* stdout);
+  static bool SynchronousExec(const std::vector<std::string>& cmd,
+                              int* return_code,
+                              std::string* stdout);
+
+  // Gets the one instance.
+  static Subprocess& Get() {
+    return *subprocess_singleton_;
+  }
+
+  // Returns true iff there is at least one subprocess we're waiting on.
+  bool SubprocessInFlight();
+
+ private:
+  FRIEND_TEST(SubprocessTest, CancelTest);
+
+  struct SubprocessRecord {
+    SubprocessRecord() = default;
+
+    uint32_t tag{0};
+    chromeos::MessageLoop::TaskId task_id{chromeos::MessageLoop::kTaskIdNull};
+
+    ExecCallback callback{nullptr};
+    void* callback_data{nullptr};
+
+    GPid pid;
+
+    int stdout_fd{-1};
+    std::string stdout;
+  };
+
+  Subprocess() {}
+
+  // Callback for when any subprocess terminates. This calls the user
+  // requested callback.
+  static void GChildExitedCallback(GPid pid, gint status, gpointer data);
+
+  // Callback which runs in the child before exec to redirect stderr onto
+  // stdout.
+  static void GRedirectStderrToStdout(gpointer user_data);
+
+  // Callback which runs whenever there is input available on the subprocess
+  // stdout pipe.
+  static void OnStdoutReady(SubprocessRecord* record);
+
+  // The global instance.
+  static Subprocess* subprocess_singleton_;
+
+  // A map from the asynchronous subprocess tag (see Exec) to the subprocess
+  // record structure for all active asynchronous subprocesses.
+  std::map<uint32_t, std::shared_ptr<SubprocessRecord>> subprocess_records_;
+
+  DISALLOW_COPY_AND_ASSIGN(Subprocess);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_SUBPROCESS_H_
diff --git a/subprocess_unittest.cc b/subprocess_unittest.cc
new file mode 100644
index 0000000..dba9e6c
--- /dev/null
+++ b/subprocess_unittest.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 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/subprocess.h"
+
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class SubprocessTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+  // TODO(deymo): Replace this with a FakeMessageLoop. Subprocess uses glib to
+  // asynchronously spawn a process, so we need to run a GlibMessageLoop here.
+  chromeos::GlibMessageLoop loop_;
+};
+
+namespace {
+int local_server_port = 0;
+
+void Callback(int return_code, const string& output, void* /* unused */) {
+  EXPECT_EQ(1, return_code);
+  MessageLoop::current()->BreakLoop();
+}
+
+void CallbackEcho(int return_code, const string& output, void* /* unused */) {
+  EXPECT_EQ(0, return_code);
+  EXPECT_NE(string::npos, output.find("this is stdout"));
+  EXPECT_NE(string::npos, output.find("this is stderr"));
+  MessageLoop::current()->BreakLoop();
+}
+
+void CallbackStdoutOnlyEcho(int return_code,
+                            const string& output,
+                            void* /* unused */) {
+  EXPECT_EQ(0, return_code);
+  EXPECT_NE(string::npos, output.find("on stdout"));
+  EXPECT_EQ(string::npos, output.find("on stderr"));
+  MessageLoop::current()->BreakLoop();
+}
+
+}  // namespace
+
+TEST_F(SubprocessTest, SimpleTest) {
+  Subprocess::Get().Exec(vector<string>{"/bin/false"}, Callback, nullptr);
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, EchoTest) {
+  Subprocess::Get().Exec(
+      vector<string>{
+          "/bin/sh",
+          "-c",
+          "echo this is stdout; echo this is stderr > /dev/stderr"},
+      CallbackEcho,
+      nullptr);
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, StderrNotIncludedInOutputTest) {
+  Subprocess::Get().ExecFlags(
+      vector<string>{"/bin/sh", "-c", "echo on stdout; echo on stderr >&2"},
+      static_cast<GSpawnFlags>(0),
+      false,  // don't redirect stderr
+      CallbackStdoutOnlyEcho,
+      nullptr);
+  loop_.Run();
+}
+
+TEST_F(SubprocessTest, SynchronousEchoTest) {
+  vector<string> cmd = {
+    "/bin/sh",
+    "-c",
+    "echo -n stdout-here; echo -n stderr-there > /dev/stderr"};
+  int rc = -1;
+  string stdout;
+  ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout));
+  EXPECT_EQ(0, rc);
+  EXPECT_EQ("stdout-herestderr-there", stdout);
+}
+
+TEST_F(SubprocessTest, SynchronousEchoNoOutputTest) {
+  vector<string> cmd = {"/bin/sh", "-c", "echo test"};
+  int rc = -1;
+  ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, nullptr));
+  EXPECT_EQ(0, rc);
+}
+
+namespace {
+void CallbackBad(int return_code, const string& output, void* p) {
+  CHECK(false) << "should never be called.";
+}
+
+// TODO(garnold) this test method uses test_http_server as a representative for
+// interactive processes that can be spawned/terminated at will. This causes us
+// to go through hoops when spawning this process (e.g. obtaining the port
+// number it uses so we can control it with wget). It would have been much
+// preferred to use something else and thus simplify both test_http_server
+// (doesn't have to be able to communicate through a temp file) and the test
+// code below; for example, it sounds like a brain dead sleep loop with proper
+// signal handlers could be used instead.
+void StartAndCancelInRunLoop(bool* spawned) {
+  // Create a temp file for test_http_server to communicate its port number.
+  char temp_file_name[] = "/tmp/subprocess_unittest-test_http_server-XXXXXX";
+  int temp_fd = mkstemp(temp_file_name);
+  CHECK_GE(temp_fd, 0);
+  int temp_flags = fcntl(temp_fd, F_GETFL, 0) | O_NONBLOCK;
+  CHECK_EQ(fcntl(temp_fd, F_SETFL, temp_flags), 0);
+
+  vector<string> cmd;
+  cmd.push_back("./test_http_server");
+  cmd.push_back(temp_file_name);
+  uint32_t tag = Subprocess::Get().Exec(cmd, CallbackBad, nullptr);
+  EXPECT_NE(0, tag);
+  *spawned = true;
+  printf("test http server spawned\n");
+  // Wait for server to be up and running
+  TimeDelta total_wait_time;
+  const TimeDelta kSleepTime = TimeDelta::FromMilliseconds(100);
+  const TimeDelta kMaxWaitTime = TimeDelta::FromSeconds(3);
+  local_server_port = 0;
+  static const char* kServerListeningMsgPrefix = "listening on port ";
+  while (total_wait_time.InMicroseconds() < kMaxWaitTime.InMicroseconds()) {
+    char line[80];
+    int line_len = read(temp_fd, line, sizeof(line) - 1);
+    if (line_len > 0) {
+      line[line_len] = '\0';
+      CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+      const char* listening_port_str =
+          line + strlen(kServerListeningMsgPrefix);
+      char* end_ptr;
+      long raw_port = strtol(listening_port_str,  // NOLINT(runtime/int)
+                             &end_ptr, 10);
+      CHECK(!*end_ptr || *end_ptr == '\n');
+      local_server_port = static_cast<in_port_t>(raw_port);
+      break;
+    } else if (line_len < 0 && errno != EAGAIN) {
+      LOG(INFO) << "error reading from " << temp_file_name << ": "
+                << strerror(errno);
+      break;
+    }
+    g_usleep(kSleepTime.InMicroseconds());
+    total_wait_time += kSleepTime;
+  }
+  close(temp_fd);
+  remove(temp_file_name);
+  CHECK_GT(local_server_port, 0);
+  LOG(INFO) << "server listening on port " << local_server_port;
+  Subprocess::Get().KillExec(tag);
+}
+
+void ExitWhenDone(bool* spawned) {
+  if (*spawned && !Subprocess::Get().SubprocessInFlight()) {
+    // tear down the sub process
+    printf("tear down time\n");
+    int status = test_utils::System(
+        base::StringPrintf("wget -O /dev/null http://127.0.0.1:%d/quitquitquit",
+                           local_server_port));
+    EXPECT_NE(-1, status) << "system() failed";
+    EXPECT_TRUE(WIFEXITED(status))
+        << "command failed to run or died abnormally";
+    MessageLoop::current()->BreakLoop();
+  } else {
+    // Re-run this callback again in 10 ms.
+    MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&ExitWhenDone, spawned),
+        TimeDelta::FromMilliseconds(10));
+  }
+}
+
+}  // namespace
+
+TEST_F(SubprocessTest, CancelTest) {
+  bool spawned = false;
+  loop_.PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&StartAndCancelInRunLoop, &spawned),
+      TimeDelta::FromMilliseconds(100));
+  loop_.PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&ExitWhenDone, &spawned),
+      TimeDelta::FromMilliseconds(10));
+  loop_.Run();
+  // This test would leak a callback that runs when the child process exits
+  // unless we wait for it to run.
+  chromeos::MessageLoopRunUntil(
+      &loop_,
+      TimeDelta::FromSeconds(10),
+      base::Bind([] {
+        return Subprocess::Get().subprocess_records_.empty();
+      }));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/system_state.h b/system_state.h
new file mode 100644
index 0000000..21aeabd
--- /dev/null
+++ b/system_state.h
@@ -0,0 +1,98 @@
+// Copyright (c) 2012 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_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_SYSTEM_STATE_H_
+
+class MetricsLibraryInterface;
+
+namespace chromeos_update_manager {
+
+class UpdateManager;
+
+}  // namespace chromeos_update_manager
+
+namespace policy {
+
+class DevicePolicy;
+
+}  // namespace policy
+
+namespace chromeos_update_engine {
+
+// SystemState is the root class within the update engine. So we should avoid
+// any circular references in header file inclusion. Hence forward-declaring
+// the required classes.
+class ClockInterface;
+class ConnectionManager;
+class GpioHandler;
+class HardwareInterface;
+class OmahaRequestParams;
+class P2PManager;
+class PayloadStateInterface;
+class PrefsInterface;
+class UpdateAttempter;
+
+// An interface to global system context, including platform resources,
+// the current state of the system, high-level objects whose lifetime is same
+// as main, system interfaces, etc.
+// Carved out separately so it can be mocked for unit tests.
+// Currently it has only one method, but we should start migrating other
+// methods to use this as and when needed to unit test them.
+// TODO(jaysri): Consider renaming this to something like GlobalContext.
+class SystemState {
+ public:
+  // Destructs this object.
+  virtual ~SystemState() {}
+
+  // Sets or gets the latest device policy.
+  virtual void set_device_policy(const policy::DevicePolicy* device_policy) = 0;
+  virtual const policy::DevicePolicy* device_policy() = 0;
+
+  // Gets the interface object for the clock.
+  virtual ClockInterface* clock() = 0;
+
+  // Gets the connection manager object.
+  virtual ConnectionManager* connection_manager() = 0;
+
+  // Gets the hardware interface object.
+  virtual HardwareInterface* hardware() = 0;
+
+  // Gets the Metrics Library interface for reporting UMA stats.
+  virtual MetricsLibraryInterface* metrics_lib() = 0;
+
+  // Gets the interface object for persisted store.
+  virtual PrefsInterface* prefs() = 0;
+
+  // Gets the interface object for the persisted store that persists across
+  // powerwashes. Please note that this should be used very seldomly and must
+  // be forwards and backwards compatible as powerwash is used to go back and
+  // forth in system versions.
+  virtual PrefsInterface* powerwash_safe_prefs() = 0;
+
+  // Gets the interface for the payload state object.
+  virtual PayloadStateInterface* payload_state() = 0;
+
+  // Returns a pointer to the update attempter object.
+  virtual UpdateAttempter* update_attempter() = 0;
+
+  // Returns a pointer to the object that stores the parameters that are
+  // common to all Omaha requests.
+  virtual OmahaRequestParams* request_params() = 0;
+
+  // Returns a pointer to the P2PManager singleton.
+  virtual P2PManager* p2p_manager() = 0;
+
+  // Returns a pointer to the UpdateManager singleton.
+  virtual chromeos_update_manager::UpdateManager* update_manager() = 0;
+
+  // If true, this is the first instance of the update engine since the system
+  // restarted. Important for tracking whether you are running instance of the
+  // update engine on first boot or due to a crash/restart.
+  virtual bool system_rebooted() = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_SYSTEM_STATE_H_
diff --git a/terminator.cc b/terminator.cc
new file mode 100644
index 0000000..6d00ddb
--- /dev/null
+++ b/terminator.cc
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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/terminator.h"
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+volatile sig_atomic_t Terminator::exit_status_ = 1;  // default exit status
+volatile sig_atomic_t Terminator::exit_blocked_ = 0;
+volatile sig_atomic_t Terminator::exit_requested_ = 0;
+
+void Terminator::Init() {
+  exit_blocked_ = 0;
+  exit_requested_ = 0;
+  signal(SIGTERM, HandleSignal);
+}
+
+void Terminator::Init(int exit_status) {
+  exit_status_ = exit_status;
+  Init();
+}
+
+void Terminator::Exit() {
+  exit(exit_status_);
+}
+
+void Terminator::HandleSignal(int signum) {
+  if (exit_blocked_ == 0) {
+    Exit();
+  }
+  exit_requested_ = 1;
+}
+
+ScopedTerminatorExitUnblocker::~ScopedTerminatorExitUnblocker() {
+  Terminator::set_exit_blocked(false);
+  if (Terminator::exit_requested()) {
+    Terminator::Exit();
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/terminator.h b/terminator.h
new file mode 100644
index 0000000..f8a261e
--- /dev/null
+++ b/terminator.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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_TERMINATOR_H_
+#define UPDATE_ENGINE_TERMINATOR_H_
+
+#include <signal.h>
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace chromeos_update_engine {
+
+// A class allowing graceful delayed exit.
+class Terminator {
+ public:
+  // Initializes the terminator and sets up signal handlers.
+  static void Init();
+  static void Init(int exit_status);
+
+  // Terminates the current process.
+  static void Exit();
+
+  // Set to true if the terminator should block termination requests in an
+  // attempt to block exiting.
+  static void set_exit_blocked(bool block) { exit_blocked_ = block ? 1 : 0; }
+  static bool exit_blocked() { return exit_blocked_ != 0; }
+
+  // Returns true if the system is trying to terminate the process, false
+  // otherwise. Returns true only if exit was blocked when the termination
+  // request arrived.
+  static bool exit_requested() { return exit_requested_ != 0; }
+
+ private:
+  FRIEND_TEST(TerminatorTest, HandleSignalTest);
+  FRIEND_TEST(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest);
+
+  // The signal handler.
+  static void HandleSignal(int signum);
+
+  static volatile sig_atomic_t exit_status_;
+  static volatile sig_atomic_t exit_blocked_;
+  static volatile sig_atomic_t exit_requested_;
+};
+
+class ScopedTerminatorExitUnblocker {
+ public:
+  ~ScopedTerminatorExitUnblocker();
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_TERMINATOR_H_
diff --git a/terminator_unittest.cc b/terminator_unittest.cc
new file mode 100644
index 0000000..5a6f4e2
--- /dev/null
+++ b/terminator_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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/terminator.h"
+
+#include <gtest/gtest.h>
+#include <gtest/gtest-spi.h>
+
+using testing::ExitedWithCode;
+
+namespace chromeos_update_engine {
+
+class TerminatorTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    Terminator::Init();
+    ASSERT_FALSE(Terminator::exit_blocked());
+    ASSERT_FALSE(Terminator::exit_requested());
+  }
+  void TearDown() override {
+    // Makes sure subsequent non-Terminator tests don't get accidentally
+    // terminated.
+    Terminator::Init();
+  }
+};
+
+typedef TerminatorTest TerminatorDeathTest;
+
+namespace {
+void UnblockExitThroughUnblocker() {
+  ScopedTerminatorExitUnblocker unblocker = ScopedTerminatorExitUnblocker();
+}
+
+void RaiseSIGTERM() {
+  ASSERT_EXIT(raise(SIGTERM), ExitedWithCode(2), "");
+}
+}  // namespace
+
+TEST_F(TerminatorTest, HandleSignalTest) {
+  Terminator::set_exit_blocked(true);
+  Terminator::HandleSignal(SIGTERM);
+  ASSERT_TRUE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorTest, ScopedTerminatorExitUnblockerTest) {
+  Terminator::set_exit_blocked(true);
+  ASSERT_TRUE(Terminator::exit_blocked());
+  ASSERT_FALSE(Terminator::exit_requested());
+  UnblockExitThroughUnblocker();
+  ASSERT_FALSE(Terminator::exit_blocked());
+  ASSERT_FALSE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorDeathTest, ExitTest) {
+  ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+  Terminator::set_exit_blocked(true);
+  ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+}
+
+TEST_F(TerminatorDeathTest, RaiseSignalTest) {
+  RaiseSIGTERM();
+  Terminator::set_exit_blocked(true);
+  EXPECT_FATAL_FAILURE(RaiseSIGTERM(), "");
+}
+
+TEST_F(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest) {
+  Terminator::set_exit_blocked(true);
+  Terminator::exit_requested_ = 1;
+  ASSERT_EXIT(UnblockExitThroughUnblocker(), ExitedWithCode(2), "");
+}
+
+}  // namespace chromeos_update_engine
diff --git a/test_http_server.cc b/test_http_server.cc
new file mode 100644
index 0000000..988599e
--- /dev/null
+++ b/test_http_server.cc
@@ -0,0 +1,613 @@
+// Copyright (c) 2012 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.
+
+// This file implements a simple HTTP server. It can exhibit odd behavior
+// that's useful for testing. For example, it's useful to test that
+// the updater can continue a connection if it's dropped, or that it
+// handles very slow data transfers.
+
+// To use this, simply make an HTTP connection to localhost:port and
+// GET a url.
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/http_common.h"
+
+
+// HTTP end-of-line delimiter; sorry, this needs to be a macro.
+#define EOL "\r\n"
+
+using std::string;
+using std::vector;
+
+
+namespace chromeos_update_engine {
+
+static const char* kListeningMsgPrefix = "listening on port ";
+
+enum {
+  RC_OK = 0,
+  RC_BAD_ARGS,
+  RC_ERR_READ,
+  RC_ERR_SETSOCKOPT,
+  RC_ERR_BIND,
+  RC_ERR_LISTEN,
+  RC_ERR_GETSOCKNAME,
+  RC_ERR_REPORT,
+};
+
+struct HttpRequest {
+  HttpRequest()
+      : start_offset(0), end_offset(0), return_code(kHttpResponseOk) {}
+  string host;
+  string url;
+  off_t start_offset;
+  off_t end_offset;  // non-inclusive, zero indicates unspecified.
+  HttpResponseCode return_code;
+};
+
+bool ParseRequest(int fd, HttpRequest* request) {
+  string headers;
+  do {
+    char buf[1024];
+    ssize_t r = read(fd, buf, sizeof(buf));
+    if (r < 0) {
+      perror("read");
+      exit(RC_ERR_READ);
+    }
+    headers.append(buf, r);
+  } while (!base::EndsWith(headers, EOL EOL, true));
+
+  LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n"
+            << headers
+            << "\n--8<------8<------8<------8<----";
+
+  // Break header into lines.
+  vector<string> lines;
+  base::SplitStringUsingSubstr(
+      headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines);
+
+  // Decode URL line.
+  vector<string> terms;
+  base::SplitStringAlongWhitespace(lines[0], &terms);
+  CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(3));
+  CHECK_EQ(terms[0], "GET");
+  request->url = terms[1];
+  LOG(INFO) << "URL: " << request->url;
+
+  // Decode remaining lines.
+  size_t i;
+  for (i = 1; i < lines.size(); i++) {
+    vector<string> terms;
+    base::SplitStringAlongWhitespace(lines[i], &terms);
+
+    if (terms[0] == "Range:") {
+      CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+      string &range = terms[1];
+      LOG(INFO) << "range attribute: " << range;
+      CHECK(base::StartsWithASCII(range, "bytes=", true) &&
+            range.find('-') != string::npos);
+      request->start_offset = atoll(range.c_str() + strlen("bytes="));
+      // Decode end offset and increment it by one (so it is non-inclusive).
+      if (range.find('-') < range.length() - 1)
+        request->end_offset = atoll(range.c_str() + range.find('-') + 1) + 1;
+      request->return_code = kHttpResponsePartialContent;
+      string tmp_str = base::StringPrintf("decoded range offsets: "
+                                               "start=%jd end=",
+                                               (intmax_t)request->start_offset);
+      if (request->end_offset > 0)
+        base::StringAppendF(&tmp_str, "%jd (non-inclusive)",
+                            (intmax_t)request->end_offset);
+      else
+        base::StringAppendF(&tmp_str, "unspecified");
+      LOG(INFO) << tmp_str;
+    } else if (terms[0] == "Host:") {
+      CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+      request->host = terms[1];
+      LOG(INFO) << "host attribute: " << request->host;
+    } else {
+      LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'";
+    }
+  }
+
+  return true;
+}
+
+string Itoa(off_t num) {
+  char buf[100] = {0};
+  snprintf(buf, sizeof(buf), "%" PRIi64, num);
+  return buf;
+}
+
+// Writes a string into a file. Returns total number of bytes written or -1 if a
+// write error occurred.
+ssize_t WriteString(int fd, const string& str) {
+  const size_t total_size = str.size();
+  size_t remaining_size = total_size;
+  char const *data = str.data();
+
+  while (remaining_size) {
+    ssize_t written = write(fd, data, remaining_size);
+    if (written < 0) {
+      perror("write");
+      LOG(INFO) << "write failed";
+      return -1;
+    }
+    data += written;
+    remaining_size -= written;
+  }
+
+  return total_size;
+}
+
+// Writes the headers of an HTTP response into a file.
+ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset,
+                     HttpResponseCode return_code) {
+  ssize_t written = 0, ret;
+
+  ret = WriteString(fd,
+                    string("HTTP/1.1 ") + Itoa(return_code) + " " +
+                    GetHttpResponseDescription(return_code) +
+                    EOL
+                    "Content-Type: application/octet-stream" EOL);
+  if (ret < 0)
+    return -1;
+  written += ret;
+
+  // Compute content legnth.
+  const off_t content_length = end_offset - start_offset;;
+
+  // A start offset that equals the end offset indicates that the response
+  // should contain the full range of bytes in the requested resource.
+  if (start_offset || start_offset == end_offset) {
+    ret = WriteString(fd,
+                      string("Accept-Ranges: bytes" EOL
+                             "Content-Range: bytes ") +
+                      Itoa(start_offset == end_offset ? 0 : start_offset) +
+                      "-" + Itoa(end_offset - 1) + "/" + Itoa(end_offset) +
+                      EOL);
+    if (ret < 0)
+      return -1;
+    written += ret;
+  }
+
+  ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) +
+                    EOL EOL);
+  if (ret < 0)
+    return -1;
+  written += ret;
+
+  return written;
+}
+
+// Writes a predetermined payload of lines of ascending bytes to a file. The
+// first byte of output is appropriately offset with respect to the request line
+// length.  Returns the number of successfully written bytes.
+size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset,
+                    const char first_byte, const size_t line_len) {
+  CHECK_LE(start_offset, end_offset);
+  CHECK_GT(line_len, static_cast<size_t>(0));
+
+  LOG(INFO) << "writing payload: " << line_len << "-byte lines starting with `"
+            << first_byte << "', offset range " << start_offset << " -> "
+            << end_offset;
+
+  // Populate line of ascending characters.
+  string line;
+  line.reserve(line_len);
+  char byte = first_byte;
+  size_t i;
+  for (i = 0; i < line_len; i++)
+    line += byte++;
+
+  const size_t total_len = end_offset - start_offset;
+  size_t remaining_len = total_len;
+  bool success = true;
+
+  // If start offset is not aligned with line boundary, output partial line up
+  // to the first line boundary.
+  size_t start_modulo = start_offset % line_len;
+  if (start_modulo) {
+    string partial = line.substr(start_modulo, remaining_len);
+    ssize_t ret = WriteString(fd, partial);
+    if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+      remaining_len -= partial.length();
+  }
+
+  // Output full lines up to the maximal line boundary below the end offset.
+  while (success && remaining_len >= line_len) {
+    ssize_t ret = WriteString(fd, line);
+    if ((success = (ret >= 0 && (size_t) ret == line_len)))
+      remaining_len -= line_len;
+  }
+
+  // Output a partial line up to the end offset.
+  if (success && remaining_len) {
+    string partial = line.substr(0, remaining_len);
+    ssize_t ret = WriteString(fd, partial);
+    if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+      remaining_len -= partial.length();
+  }
+
+  return (total_len - remaining_len);
+}
+
+// Write default payload lines of the form 'abcdefghij'.
+inline size_t WritePayload(int fd, const off_t start_offset,
+                           const off_t end_offset) {
+  return WritePayload(fd, start_offset, end_offset, 'a', 10);
+}
+
+// Send an empty response, then kill the server.
+void HandleQuit(int fd) {
+  WriteHeaders(fd, 0, 0, kHttpResponseOk);
+  LOG(INFO) << "pid(" << getpid() <<  "): HTTP server exiting ...";
+  exit(RC_OK);
+}
+
+
+// Generates an HTTP response with payload corresponding to requested offsets
+// and length.  Optionally, truncate the payload at a given length and add a
+// pause midway through the transfer.  Returns the total number of bytes
+// delivered or -1 for error.
+ssize_t HandleGet(int fd, const HttpRequest& request, const size_t total_length,
+                  const size_t truncate_length, const int sleep_every,
+                  const int sleep_secs) {
+  ssize_t ret;
+  size_t written = 0;
+
+  // Obtain start offset, make sure it is within total payload length.
+  const size_t start_offset = request.start_offset;
+  if (start_offset >= total_length) {
+    LOG(WARNING) << "start offset (" << start_offset
+                 << ") exceeds total length (" << total_length
+                 << "), generating error response ("
+                 << kHttpResponseReqRangeNotSat << ")";
+    return WriteHeaders(fd, total_length, total_length,
+                        kHttpResponseReqRangeNotSat);
+  }
+
+  // Obtain end offset, adjust to fit in total payload length and ensure it does
+  // not preceded the start offset.
+  size_t end_offset = (request.end_offset > 0 ?
+                       request.end_offset : total_length);
+  if (end_offset < start_offset) {
+    LOG(WARNING) << "end offset (" << end_offset << ") precedes start offset ("
+                 << start_offset << "), generating error response";
+    return WriteHeaders(fd, 0, 0, kHttpResponseBadRequest);
+  }
+  if (end_offset > total_length) {
+    LOG(INFO) << "requested end offset (" << end_offset
+              << ") exceeds total length (" << total_length << "), adjusting";
+    end_offset = total_length;
+  }
+
+  // Generate headers
+  LOG(INFO) << "generating response header: range=" << start_offset << "-"
+            << (end_offset - 1) << "/" << (end_offset - start_offset)
+            << ", return code=" << request.return_code;
+  if ((ret = WriteHeaders(fd, start_offset, end_offset,
+                          request.return_code)) < 0)
+    return -1;
+  LOG(INFO) << ret << " header bytes written";
+  written += ret;
+
+  // Compute payload length, truncate as necessary.
+  size_t payload_length = end_offset - start_offset;
+  if (truncate_length > 0 && truncate_length < payload_length) {
+    LOG(INFO) << "truncating request payload length (" << payload_length
+              << ") at " << truncate_length;
+    payload_length = truncate_length;
+    end_offset = start_offset + payload_length;
+  }
+
+  LOG(INFO) << "generating response payload: range=" << start_offset << "-"
+            << (end_offset - 1) << "/" << (end_offset - start_offset);
+
+  // Decide about optional midway delay.
+  if (truncate_length > 0 && sleep_every > 0 && sleep_secs >= 0 &&
+      start_offset % (truncate_length * sleep_every) == 0) {
+    const off_t midway_offset = start_offset + payload_length / 2;
+
+    if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0)
+      return -1;
+    LOG(INFO) << ret << " payload bytes written (first chunk)";
+    written += ret;
+
+    LOG(INFO) << "sleeping for " << sleep_secs << " seconds...";
+    sleep(sleep_secs);
+
+    if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0)
+      return -1;
+    LOG(INFO) << ret << " payload bytes written (second chunk)";
+    written += ret;
+  } else {
+    if ((ret = WritePayload(fd, start_offset, end_offset)) < 0)
+      return -1;
+    LOG(INFO) << ret << " payload bytes written";
+    written += ret;
+  }
+
+  LOG(INFO) << "response generation complete, " << written
+            << " total bytes written";
+  return written;
+}
+
+ssize_t HandleGet(int fd, const HttpRequest& request,
+                  const size_t total_length) {
+  return HandleGet(fd, request, total_length, 0, 0, 0);
+}
+
+// Handles /redirect/<code>/<url> requests by returning the specified
+// redirect <code> with a location pointing to /<url>.
+void HandleRedirect(int fd, const HttpRequest& request) {
+  LOG(INFO) << "Redirecting...";
+  string url = request.url;
+  CHECK_EQ(static_cast<size_t>(0), url.find("/redirect/"));
+  url.erase(0, strlen("/redirect/"));
+  string::size_type url_start = url.find('/');
+  CHECK_NE(url_start, string::npos);
+  HttpResponseCode code = StringToHttpResponseCode(url.c_str());
+  url.erase(0, url_start);
+  url = "http://" + request.host + url;
+  const char *status = GetHttpResponseDescription(code);
+  if (!status)
+    CHECK(false) << "Unrecognized redirection code: " << code;
+  LOG(INFO) << "Code: " << code << " " << status;
+  LOG(INFO) << "New URL: " << url;
+
+  ssize_t ret;
+  if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " +
+                         status + EOL)) < 0)
+    return;
+  WriteString(fd, "Location: " + url + EOL);
+}
+
+// Generate a page not found error response with actual text payload. Return
+// number of bytes written or -1 for error.
+ssize_t HandleError(int fd, const HttpRequest& request) {
+  LOG(INFO) << "Generating error HTTP response";
+
+  ssize_t ret;
+  size_t written = 0;
+
+  const string data("This is an error page.");
+
+  if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0)
+    return -1;
+  written += ret;
+
+  if ((ret = WriteString(fd, data)) < 0)
+    return -1;
+  written += ret;
+
+  return written;
+}
+
+// Generate an error response if the requested offset is nonzero, up to a given
+// maximal number of successive failures.  The error generated is an "Internal
+// Server Error" (500).
+ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request,
+                            size_t end_offset, int max_fails) {
+  static int num_fails = 0;
+
+  if (request.start_offset > 0 && num_fails < max_fails) {
+    LOG(INFO) << "Generating error HTTP response";
+
+    ssize_t ret;
+    size_t written = 0;
+
+    const string data("This is an error page.");
+
+    if ((ret = WriteHeaders(fd, 0, data.size(),
+                            kHttpResponseInternalServerError)) < 0)
+      return -1;
+    written += ret;
+
+    if ((ret = WriteString(fd, data)) < 0)
+      return -1;
+    written += ret;
+
+    num_fails++;
+    return written;
+  } else {
+    num_fails = 0;
+    return HandleGet(fd, request, end_offset);
+  }
+}
+
+void HandleDefault(int fd, const HttpRequest& request) {
+  const off_t start_offset = request.start_offset;
+  const string data("unhandled path");
+  const size_t size = data.size();
+  ssize_t ret;
+
+  if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0)
+    return;
+  WriteString(fd, (start_offset < static_cast<off_t>(size) ?
+                   data.substr(start_offset) : ""));
+}
+
+
+// Break a URL into terms delimited by slashes.
+class UrlTerms {
+ public:
+  UrlTerms(const string &url, size_t num_terms) {
+    // URL must be non-empty and start with a slash.
+    CHECK_GT(url.size(), static_cast<size_t>(0));
+    CHECK_EQ(url[0], '/');
+
+    // Split it into terms delimited by slashes, omitting the preceeding slash.
+    base::SplitStringDontTrim(url.substr(1), '/', &terms);
+
+    // Ensure expected length.
+    CHECK_EQ(terms.size(), num_terms);
+  }
+
+  inline string Get(const off_t index) const {
+    return terms[index];
+  }
+  inline const char *GetCStr(const off_t index) const {
+    return Get(index).c_str();
+  }
+  inline int GetInt(const off_t index) const {
+    return atoi(GetCStr(index));
+  }
+  inline size_t GetSizeT(const off_t index) const {
+    return static_cast<size_t>(atol(GetCStr(index)));
+  }
+
+ private:
+  vector<string> terms;
+};
+
+void HandleConnection(int fd) {
+  HttpRequest request;
+  ParseRequest(fd, &request);
+
+  string &url = request.url;
+  LOG(INFO) << "pid(" << getpid() <<  "): handling url " << url;
+  if (url == "/quitquitquit") {
+    HandleQuit(fd);
+  } else if (base::StartsWithASCII(url, "/download/", true)) {
+    const UrlTerms terms(url, 2);
+    HandleGet(fd, request, terms.GetSizeT(1));
+  } else if (base::StartsWithASCII(url, "/flaky/", true)) {
+    const UrlTerms terms(url, 5);
+    HandleGet(fd, request, terms.GetSizeT(1), terms.GetSizeT(2),
+              terms.GetInt(3), terms.GetInt(4));
+  } else if (url.find("/redirect/") == 0) {
+    HandleRedirect(fd, request);
+  } else if (url == "/error") {
+    HandleError(fd, request);
+  } else if (base::StartsWithASCII(url, "/error-if-offset/", true)) {
+    const UrlTerms terms(url, 3);
+    HandleErrorIfOffset(fd, request, terms.GetSizeT(1), terms.GetInt(2));
+  } else {
+    HandleDefault(fd, request);
+  }
+
+  close(fd);
+}
+
+}  // namespace chromeos_update_engine
+
+using namespace chromeos_update_engine;  // NOLINT(build/namespaces)
+
+
+void usage(const char *prog_arg) {
+  fprintf(
+      stderr,
+      "Usage: %s [ FILE ]\n"
+      "Once accepting connections, the following is written to FILE (or "
+      "stdout):\n"
+      "\"%sN\" (where N is an integer port number)\n",
+      basename(prog_arg), kListeningMsgPrefix);
+}
+
+int main(int argc, char** argv) {
+  // Check invocation.
+  if (argc > 2)
+    errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)");
+
+  // Parse (optional) argument.
+  int report_fd = STDOUT_FILENO;
+  if (argc == 2) {
+    if (!strcmp(argv[1], "-h")) {
+      usage(argv[0]);
+      exit(RC_OK);
+    }
+
+    report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644);
+  }
+
+  // Ignore SIGPIPE on write() to sockets.
+  signal(SIGPIPE, SIG_IGN);
+
+  int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
+  if (listen_fd < 0)
+    LOG(FATAL) << "socket() failed";
+
+  struct sockaddr_in server_addr = sockaddr_in();
+  server_addr.sin_family = AF_INET;
+  server_addr.sin_addr.s_addr = INADDR_ANY;
+  server_addr.sin_port = 0;
+
+  {
+    // Get rid of "Address in use" error
+    int tr = 1;
+    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
+                   sizeof(int)) == -1) {
+      perror("setsockopt");
+      exit(RC_ERR_SETSOCKOPT);
+    }
+  }
+
+  // Bind the socket and set for listening.
+  if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
+           sizeof(server_addr)) < 0) {
+    perror("bind");
+    exit(RC_ERR_BIND);
+  }
+  if (listen(listen_fd, 5) < 0) {
+    perror("listen");
+    exit(RC_ERR_LISTEN);
+  }
+
+  // Check the actual port.
+  struct sockaddr_in bound_addr = sockaddr_in();
+  socklen_t bound_addr_len = sizeof(bound_addr);
+  if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr),
+                  &bound_addr_len) < 0) {
+    perror("getsockname");
+    exit(RC_ERR_GETSOCKNAME);
+  }
+  in_port_t port = ntohs(bound_addr.sin_port);
+
+  // Output the listening port, indicating that the server is processing
+  // requests. IMPORTANT! (a) the format of this message is as expected by some
+  // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the
+  // file to prevent the spawning process from waiting indefinitely for this
+  // message.
+  string listening_msg = base::StringPrintf("%s%hu", kListeningMsgPrefix, port);
+  LOG(INFO) << listening_msg;
+  CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()),
+           static_cast<int>(listening_msg.length()));
+  CHECK_EQ(write(report_fd, "\n", 1), 1);
+  if (report_fd == STDOUT_FILENO)
+    fsync(report_fd);
+  else
+    close(report_fd);
+
+  while (1) {
+    LOG(INFO) << "pid(" << getpid() <<  "): waiting to accept new connection";
+    int client_fd = accept(listen_fd, nullptr, nullptr);
+    LOG(INFO) << "got past accept";
+    if (client_fd < 0)
+      LOG(FATAL) << "ERROR on accept";
+    HandleConnection(client_fd);
+  }
+  return 0;
+}
diff --git a/test_utils.cc b/test_utils.cc
new file mode 100644
index 0000000..0c02279
--- /dev/null
+++ b/test_utils.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 2012 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/test_utils.h"
+
+#include <attr/xattr.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/file_writer.h"
+#include "update_engine/utils.h"
+
+using base::StringPrintf;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void PrintTo(const Extent& extent, ::std::ostream* os) {
+  *os << "(" << extent.start_block() << ", " << extent.num_blocks() << ")";
+}
+
+namespace test_utils {
+
+const char* const kMountPathTemplate = "UpdateEngineTests_mnt-XXXXXX";
+
+const uint8_t kRandomString[] = {
+  0xf2, 0xb7, 0x55, 0x92, 0xea, 0xa6, 0xc9, 0x57,
+  0xe0, 0xf8, 0xeb, 0x34, 0x93, 0xd9, 0xc4, 0x8f,
+  0xcb, 0x20, 0xfa, 0x37, 0x4b, 0x40, 0xcf, 0xdc,
+  0xa5, 0x08, 0x70, 0x89, 0x79, 0x35, 0xe2, 0x3d,
+  0x56, 0xa4, 0x75, 0x73, 0xa3, 0x6d, 0xd1, 0xd5,
+  0x26, 0xbb, 0x9c, 0x60, 0xbd, 0x2f, 0x5a, 0xfa,
+  0xb7, 0xd4, 0x3a, 0x50, 0xa7, 0x6b, 0x3e, 0xfd,
+  0x61, 0x2b, 0x3a, 0x31, 0x30, 0x13, 0x33, 0x53,
+  0xdb, 0xd0, 0x32, 0x71, 0x5c, 0x39, 0xed, 0xda,
+  0xb4, 0x84, 0xca, 0xbc, 0xbd, 0x78, 0x1c, 0x0c,
+  0xd8, 0x0b, 0x41, 0xe8, 0xe1, 0xe0, 0x41, 0xad,
+  0x03, 0x12, 0xd3, 0x3d, 0xb8, 0x75, 0x9b, 0xe6,
+  0xd9, 0x01, 0xd0, 0x87, 0xf4, 0x36, 0xfa, 0xa7,
+  0x0a, 0xfa, 0xc5, 0x87, 0x65, 0xab, 0x9a, 0x7b,
+  0xeb, 0x58, 0x23, 0xf0, 0xa8, 0x0a, 0xf2, 0x33,
+  0x3a, 0xe2, 0xe3, 0x35, 0x74, 0x95, 0xdd, 0x3c,
+  0x59, 0x5a, 0xd9, 0x52, 0x3a, 0x3c, 0xac, 0xe5,
+  0x15, 0x87, 0x6d, 0x82, 0xbc, 0xf8, 0x7d, 0xbe,
+  0xca, 0xd3, 0x2c, 0xd6, 0xec, 0x38, 0xeb, 0xe4,
+  0x53, 0xb0, 0x4c, 0x3f, 0x39, 0x29, 0xf7, 0xa4,
+  0x73, 0xa8, 0xcb, 0x32, 0x50, 0x05, 0x8c, 0x1c,
+  0x1c, 0xca, 0xc9, 0x76, 0x0b, 0x8f, 0x6b, 0x57,
+  0x1f, 0x24, 0x2b, 0xba, 0x82, 0xba, 0xed, 0x58,
+  0xd8, 0xbf, 0xec, 0x06, 0x64, 0x52, 0x6a, 0x3f,
+  0xe4, 0xad, 0xce, 0x84, 0xb4, 0x27, 0x55, 0x14,
+  0xe3, 0x75, 0x59, 0x73, 0x71, 0x51, 0xea, 0xe8,
+  0xcc, 0xda, 0x4f, 0x09, 0xaf, 0xa4, 0xbc, 0x0e,
+  0xa6, 0x1f, 0xe2, 0x3a, 0xf8, 0x96, 0x7d, 0x30,
+  0x23, 0xc5, 0x12, 0xb5, 0xd8, 0x73, 0x6b, 0x71,
+  0xab, 0xf1, 0xd7, 0x43, 0x58, 0xa7, 0xc9, 0xf0,
+  0xe4, 0x85, 0x1c, 0xd6, 0x92, 0x50, 0x2c, 0x98,
+  0x36, 0xfe, 0x87, 0xaf, 0x43, 0x8f, 0x8f, 0xf5,
+  0x88, 0x48, 0x18, 0x42, 0xcf, 0x42, 0xc1, 0xa8,
+  0xe8, 0x05, 0x08, 0xa1, 0x45, 0x70, 0x5b, 0x8c,
+  0x39, 0x28, 0xab, 0xe9, 0x6b, 0x51, 0xd2, 0xcb,
+  0x30, 0x04, 0xea, 0x7d, 0x2f, 0x6e, 0x6c, 0x3b,
+  0x5f, 0x82, 0xd9, 0x5b, 0x89, 0x37, 0x65, 0x65,
+  0xbe, 0x9f, 0xa3, 0x5d,
+};
+
+bool IsXAttrSupported(const base::FilePath& dir_path) {
+  char *path = strdup(dir_path.Append("xattr_test_XXXXXX").value().c_str());
+
+  int fd = mkstemp(path);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error creating temporary file in " << dir_path.value();
+    free(path);
+    return false;
+  }
+
+  if (unlink(path) != 0) {
+    PLOG(ERROR) << "Error unlinking temporary file " << path;
+    close(fd);
+    free(path);
+    return false;
+  }
+
+  int xattr_res = fsetxattr(fd, "user.xattr-test", "value", strlen("value"), 0);
+  if (xattr_res != 0) {
+    if (errno == ENOTSUP) {
+      // Leave it to call-sites to warn about non-support.
+    } else {
+      PLOG(ERROR) << "Error setting xattr on " << path;
+    }
+  }
+  close(fd);
+  free(path);
+  return xattr_res == 0;
+}
+
+bool WriteFileVector(const string& path, const chromeos::Blob& data) {
+  return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+bool WriteFileString(const string& path, const string& data) {
+  return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+// Binds provided |filename| to an unused loopback device, whose name is written
+// to the string pointed to by |lo_dev_name_p|. Returns true on success, false
+// otherwise (along with corresponding test failures), in which case the content
+// of |lo_dev_name_p| is unknown.
+bool BindToUnusedLoopDevice(const string& filename, string* lo_dev_name_p) {
+  CHECK(lo_dev_name_p);
+
+  // Bind to an unused loopback device, sanity check the device name.
+  lo_dev_name_p->clear();
+  if (!(utils::ReadPipe("losetup --show -f " + filename, lo_dev_name_p) &&
+        base::StartsWithASCII(*lo_dev_name_p, "/dev/loop", true))) {
+    ADD_FAILURE();
+    return false;
+  }
+
+  // Strip anything from the first newline char.
+  size_t newline_pos = lo_dev_name_p->find('\n');
+  if (newline_pos != string::npos)
+    lo_dev_name_p->erase(newline_pos);
+
+  return true;
+}
+
+bool ExpectVectorsEq(const chromeos::Blob& expected,
+                     const chromeos::Blob& actual) {
+  EXPECT_EQ(expected.size(), actual.size());
+  if (expected.size() != actual.size())
+    return false;
+  bool is_all_eq = true;
+  for (unsigned int i = 0; i < expected.size(); i++) {
+    EXPECT_EQ(expected[i], actual[i]) << "offset: " << i;
+    is_all_eq = is_all_eq && (expected[i] == actual[i]);
+  }
+  return is_all_eq;
+}
+
+void FillWithData(chromeos::Blob* buffer) {
+  size_t input_counter = 0;
+  for (uint8_t& b : *buffer) {
+    b = kRandomString[input_counter];
+    input_counter++;
+    input_counter %= sizeof(kRandomString);
+  }
+}
+
+void CreateEmptyExtImageAtPath(const string& path,
+                               size_t size,
+                               int block_size) {
+  EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
+                                   " seek=%zu bs=1 count=1 status=none",
+                                   path.c_str(), size)));
+  EXPECT_EQ(0, System(StringPrintf("mkfs.ext3 -q -b %d -F %s",
+                                   block_size, path.c_str())));
+}
+
+void CreateExtImageAtPath(const string& path, vector<string>* out_paths) {
+  // create 10MiB sparse file, mounted at a unique location.
+  string mount_path;
+  CHECK(utils::MakeTempDirectory(kMountPathTemplate, &mount_path));
+  ScopedDirRemover mount_path_unlinker(mount_path);
+
+  EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
+                                   " seek=10485759 bs=1 count=1 status=none",
+                                   path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkfs.ext3 -q -b 4096 -F %s",
+                                   path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mount -o loop %s %s", path.c_str(),
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("echo hi > %s/hi", mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("echo hello > %s/hello",
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir", mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/empty_dir",
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/mnt",
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("echo T > %s/some_dir/test",
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mkfifo %s/some_dir/fifo",
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("mknod %s/cdev c 2 3", mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("ln -s /some/target %s/sym",
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("ln %s/some_dir/test %s/testlink",
+                                   mount_path.c_str(), mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("echo T > %s/srchardlink0",
+                                   mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("ln %s/srchardlink0 %s/srchardlink1",
+                                   mount_path.c_str(), mount_path.c_str())));
+  EXPECT_EQ(0, System(StringPrintf("ln -s bogus %s/boguslink",
+                                   mount_path.c_str())));
+  EXPECT_TRUE(utils::UnmountFilesystem(mount_path.c_str()));
+
+  if (out_paths) {
+    out_paths->clear();
+    out_paths->push_back("");
+    out_paths->push_back("/hi");
+    out_paths->push_back("/boguslink");
+    out_paths->push_back("/hello");
+    out_paths->push_back("/some_dir");
+    out_paths->push_back("/some_dir/empty_dir");
+    out_paths->push_back("/some_dir/mnt");
+    out_paths->push_back("/some_dir/test");
+    out_paths->push_back("/some_dir/fifo");
+    out_paths->push_back("/cdev");
+    out_paths->push_back("/testlink");
+    out_paths->push_back("/sym");
+    out_paths->push_back("/srchardlink0");
+    out_paths->push_back("/srchardlink1");
+    out_paths->push_back("/lost+found");
+  }
+}
+
+ScopedLoopMounter::ScopedLoopMounter(const string& file_path,
+                                     string* mnt_path,
+                                     unsigned long flags) {  // NOLINT - long
+  EXPECT_TRUE(utils::MakeTempDirectory("mnt.XXXXXX", mnt_path));
+  dir_remover_.reset(new ScopedDirRemover(*mnt_path));
+
+  string loop_dev;
+  loop_binder_.reset(new ScopedLoopbackDeviceBinder(file_path, &loop_dev));
+
+  EXPECT_TRUE(utils::MountFilesystem(loop_dev, *mnt_path, flags));
+  unmounter_.reset(new ScopedFilesystemUnmounter(*mnt_path));
+}
+
+namespace {
+class ScopedDirCloser {
+ public:
+  explicit ScopedDirCloser(DIR** dir) : dir_(dir) {}
+  ~ScopedDirCloser() {
+    if (dir_ && *dir_) {
+      int r = closedir(*dir_);
+      TEST_AND_RETURN_ERRNO(r == 0);
+      *dir_ = nullptr;
+      dir_ = nullptr;
+    }
+  }
+ private:
+  DIR** dir_;
+};
+}  // namespace
+
+bool RecursiveUnlinkDir(const string& path) {
+  struct stat stbuf;
+  int r = lstat(path.c_str(), &stbuf);
+  TEST_AND_RETURN_FALSE_ERRNO((r == 0) || (errno == ENOENT));
+  if ((r < 0) && (errno == ENOENT))
+    // path request is missing. that's fine.
+    return true;
+  if (!S_ISDIR(stbuf.st_mode)) {
+    TEST_AND_RETURN_FALSE_ERRNO((unlink(path.c_str()) == 0) ||
+                                (errno == ENOENT));
+    // success or path disappeared before we could unlink.
+    return true;
+  }
+  {
+    // We have a dir, unlink all children, then delete dir
+    DIR *dir = opendir(path.c_str());
+    TEST_AND_RETURN_FALSE_ERRNO(dir);
+    ScopedDirCloser dir_closer(&dir);
+    struct dirent dir_entry;
+    struct dirent *dir_entry_p;
+    int err = 0;
+    while ((err = readdir_r(dir, &dir_entry, &dir_entry_p)) == 0) {
+      if (dir_entry_p == nullptr) {
+        // end of stream reached
+        break;
+      }
+      // Skip . and ..
+      if (!strcmp(dir_entry_p->d_name, ".") ||
+          !strcmp(dir_entry_p->d_name, ".."))
+        continue;
+      TEST_AND_RETURN_FALSE(RecursiveUnlinkDir(path + "/" +
+                                               dir_entry_p->d_name));
+    }
+    TEST_AND_RETURN_FALSE(err == 0);
+  }
+  // unlink dir
+  TEST_AND_RETURN_FALSE_ERRNO((rmdir(path.c_str()) == 0) || (errno == ENOENT));
+  return true;
+}
+
+GValue* GValueNewString(const char* str) {
+  GValue* gval = g_new0(GValue, 1);
+  g_value_init(gval, G_TYPE_STRING);
+  g_value_set_string(gval, str);
+  return gval;
+}
+
+void GValueFree(gpointer arg) {
+  auto gval = reinterpret_cast<GValue*>(arg);
+  g_value_unset(gval);
+  g_free(gval);
+}
+
+base::FilePath GetBuildArtifactsPath() {
+  base::FilePath exe_path;
+  base::ReadSymbolicLink(base::FilePath("/proc/self/exe"), &exe_path);
+  return exe_path.DirName();
+}
+
+}  // namespace test_utils
+}  // namespace chromeos_update_engine
diff --git a/test_utils.h b/test_utils.h
new file mode 100644
index 0000000..13bbd41
--- /dev/null
+++ b/test_utils.h
@@ -0,0 +1,271 @@
+// Copyright (c) 2012 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_TEST_UTILS_H_
+#define UPDATE_ENGINE_TEST_UTILS_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// Streams used for gtest's PrintTo() functions.
+#include <iostream>  // NOLINT(readability/streams)
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <glib-object.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/action.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/update_metadata.pb.h"
+#include "update_engine/utils.h"
+
+// These are some handy functions for unittests.
+
+namespace chromeos_update_engine {
+
+// PrintTo() functions are used by gtest to log these objects. These PrintTo()
+// functions must be defined in the same namespace as the first argument.
+void PrintTo(const Extent& extent, ::std::ostream* os);
+
+namespace test_utils {
+
+// 300 byte pseudo-random string. Not null terminated.
+// This does not gzip compress well.
+extern const uint8_t kRandomString[300];
+
+// Writes the data passed to path. The file at path will be overwritten if it
+// exists. Returns true on success, false otherwise.
+bool WriteFileVector(const std::string& path, const chromeos::Blob& data);
+bool WriteFileString(const std::string& path, const std::string& data);
+
+bool BindToUnusedLoopDevice(const std::string &filename,
+                            std::string* lo_dev_name_ptr);
+
+// Returns true iff a == b
+bool ExpectVectorsEq(const chromeos::Blob& a, const chromeos::Blob& b);
+
+inline int System(const std::string& cmd) {
+  return system(cmd.c_str());
+}
+
+inline int Symlink(const std::string& oldpath, const std::string& newpath) {
+  return symlink(oldpath.c_str(), newpath.c_str());
+}
+
+inline int Chmod(const std::string& path, mode_t mode) {
+  return chmod(path.c_str(), mode);
+}
+
+inline int Mkdir(const std::string& path, mode_t mode) {
+  return mkdir(path.c_str(), mode);
+}
+
+inline int Chdir(const std::string& path) {
+  return chdir(path.c_str());
+}
+
+// Checks if xattr is supported in the directory specified by
+// |dir_path| which must be writable. Returns true if the feature is
+// supported, false if not or if an error occurred.
+bool IsXAttrSupported(const base::FilePath& dir_path);
+
+void FillWithData(chromeos::Blob* buffer);
+
+// Creates an empty ext image.
+void CreateEmptyExtImageAtPath(const std::string& path,
+                               size_t size,
+                               int block_size);
+
+// Creates an ext image with some files in it. The paths creates are
+// returned in out_paths.
+void CreateExtImageAtPath(const std::string& path,
+                          std::vector<std::string>* out_paths);
+
+// Class to unmount FS when object goes out of scope
+class ScopedFilesystemUnmounter {
+ public:
+  explicit ScopedFilesystemUnmounter(const std::string& mountpoint)
+      : mountpoint_(mountpoint),
+        should_unmount_(true) {}
+  ~ScopedFilesystemUnmounter() {
+    if (should_unmount_) {
+      utils::UnmountFilesystem(mountpoint_);
+    }
+  }
+  void set_should_unmount(bool unmount) { should_unmount_ = unmount; }
+ private:
+  const std::string mountpoint_;
+  bool should_unmount_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedFilesystemUnmounter);
+};
+
+class ScopedLoopbackDeviceBinder {
+ public:
+  ScopedLoopbackDeviceBinder(const std::string& file, std::string* dev) {
+    is_bound_ = BindToUnusedLoopDevice(file, &dev_);
+    EXPECT_TRUE(is_bound_);
+
+    if (is_bound_ && dev)
+      *dev = dev_;
+  }
+
+  ~ScopedLoopbackDeviceBinder() {
+    if (!is_bound_)
+      return;
+
+    for (int retry = 0; retry < 5; retry++) {
+      std::vector<std::string> args;
+      args.push_back("/sbin/losetup");
+      args.push_back("-d");
+      args.push_back(dev_);
+      int return_code = 0;
+      EXPECT_TRUE(Subprocess::SynchronousExec(args, &return_code, nullptr));
+      if (return_code == 0) {
+        return;
+      }
+      sleep(1);
+    }
+    ADD_FAILURE();
+  }
+
+  const std::string &dev() {
+    EXPECT_TRUE(is_bound_);
+    return dev_;
+  }
+
+  bool is_bound() const { return is_bound_; }
+
+ private:
+  std::string dev_;
+  bool is_bound_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceBinder);
+};
+
+class ScopedTempFile {
+ public:
+  ScopedTempFile() {
+    EXPECT_TRUE(utils::MakeTempFile("/tmp/update_engine_test_temp_file.XXXXXX",
+                                    &path_,
+                                    nullptr));
+    unlinker_.reset(new ScopedPathUnlinker(path_));
+  }
+  const std::string& GetPath() { return path_; }
+ private:
+  std::string path_;
+  std::unique_ptr<ScopedPathUnlinker> unlinker_;
+};
+
+class ScopedLoopMounter {
+ public:
+  explicit ScopedLoopMounter(const std::string& file_path,
+                             std::string* mnt_path,
+                             unsigned long flags);  // NOLINT(runtime/int)
+
+ private:
+  // These objects must be destructed in the following order:
+  //   ScopedFilesystemUnmounter (the file system must be unmounted first)
+  //   ScopedLoopbackDeviceBinder (then the loop device can be deleted)
+  //   ScopedDirRemover (then the mount point can be deleted)
+  std::unique_ptr<ScopedDirRemover> dir_remover_;
+  std::unique_ptr<ScopedLoopbackDeviceBinder> loop_binder_;
+  std::unique_ptr<ScopedFilesystemUnmounter> unmounter_;
+};
+
+// Deletes a directory and all its contents synchronously. Returns true
+// on success. This may be called with a regular file--it will just unlink it.
+// This WILL cross filesystem boundaries.
+bool RecursiveUnlinkDir(const std::string& path);
+
+// Allocates, initializes and returns a string GValue object.
+GValue* GValueNewString(const char* str);
+
+// Frees a GValue object and its allocated resources.
+void GValueFree(gpointer arg);
+
+// Returns the path where the build artifacts are stored. This is the directory
+// where the unittest executable is being run from.
+base::FilePath GetBuildArtifactsPath();
+
+}  // namespace test_utils
+
+// Useful actions for test. These need to be defined in the
+// chromeos_update_engine namespace.
+
+class NoneType;
+
+template<typename T>
+class ObjectFeederAction;
+
+template<typename T>
+class ActionTraits<ObjectFeederAction<T>> {
+ public:
+  typedef T OutputObjectType;
+  typedef NoneType InputObjectType;
+};
+
+// This is a simple Action class for testing. It feeds an object into
+// another action.
+template<typename T>
+class ObjectFeederAction : public Action<ObjectFeederAction<T>> {
+ public:
+  typedef NoneType InputObjectType;
+  typedef T OutputObjectType;
+  void PerformAction() {
+    LOG(INFO) << "feeder running!";
+    CHECK(this->processor_);
+    if (this->HasOutputPipe()) {
+      this->SetOutputObject(out_obj_);
+    }
+    this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  static std::string StaticType() { return "ObjectFeederAction"; }
+  std::string Type() const { return StaticType(); }
+  void set_obj(const T& out_obj) {
+    out_obj_ = out_obj;
+  }
+ private:
+  T out_obj_;
+};
+
+template<typename T>
+class ObjectCollectorAction;
+
+template<typename T>
+class ActionTraits<ObjectCollectorAction<T>> {
+ public:
+  typedef NoneType OutputObjectType;
+  typedef T InputObjectType;
+};
+
+// This is a simple Action class for testing. It receives an object from
+// another action.
+template<typename T>
+class ObjectCollectorAction : public Action<ObjectCollectorAction<T>> {
+ public:
+  typedef T InputObjectType;
+  typedef NoneType OutputObjectType;
+  void PerformAction() {
+    LOG(INFO) << "collector running!";
+    ASSERT_TRUE(this->processor_);
+    if (this->HasInputObject()) {
+      object_ = this->GetInputObject();
+    }
+    this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+  }
+  static std::string StaticType() { return "ObjectCollectorAction"; }
+  std::string Type() const { return StaticType(); }
+  const T& object() const { return object_; }
+ private:
+  T object_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_TEST_UTILS_H_
diff --git a/test_utils_unittest.cc b/test_utils_unittest.cc
new file mode 100644
index 0000000..ad41814
--- /dev/null
+++ b/test_utils_unittest.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 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/test_utils.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+namespace test_utils {
+
+class TestUtilsTest : public ::testing::Test { };
+
+TEST(UtilsTest, RecursiveUnlinkDirTest) {
+  string first_dir_name;
+  ASSERT_TRUE(utils::MakeTempDirectory("RecursiveUnlinkDirTest-a-XXXXXX",
+                                       &first_dir_name));
+  ASSERT_EQ(0, Chmod(first_dir_name, 0755));
+  string second_dir_name;
+  ASSERT_TRUE(utils::MakeTempDirectory("RecursiveUnlinkDirTest-b-XXXXXX",
+                                       &second_dir_name));
+  ASSERT_EQ(0, Chmod(second_dir_name, 0755));
+
+  EXPECT_EQ(0, Symlink(string("../") + first_dir_name,
+                       second_dir_name + "/link"));
+  EXPECT_EQ(0, System(string("echo hi > ") + second_dir_name + "/file"));
+  EXPECT_EQ(0, Mkdir(second_dir_name + "/dir", 0755));
+  EXPECT_EQ(0, System(string("echo ok > ") + second_dir_name + "/dir/subfile"));
+  EXPECT_TRUE(test_utils::RecursiveUnlinkDir(second_dir_name));
+  EXPECT_TRUE(utils::FileExists(first_dir_name.c_str()));
+  EXPECT_EQ(0, System(string("rm -rf ") + first_dir_name));
+  EXPECT_FALSE(utils::FileExists(second_dir_name.c_str()));
+  EXPECT_TRUE(test_utils::RecursiveUnlinkDir("/something/that/doesnt/exist"));
+}
+
+}  // namespace test_utils
+}  // namespace chromeos_update_engine
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..4786c74
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+// based on pam_google_testrunner.cc
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <chromeos/test_helpers.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/subprocess.h"
+#include "update_engine/terminator.h"
+
+int main(int argc, char **argv) {
+  LOG(INFO) << "started";
+  ::g_type_init();
+  dbus_threads_init_default();
+  base::AtExitManager exit_manager;
+  // TODO(garnold) temporarily cause the unittest binary to exit with status
+  // code 2 upon catching a SIGTERM. This will help diagnose why the unittest
+  // binary is perceived as failing by the buildbot.  We should revert it to use
+  // the default exit status of 1.  Corresponding reverts are necessary in
+  // terminator_unittest.cc.
+  chromeos_update_engine::Terminator::Init(2);
+  chromeos_update_engine::Subprocess::Init();
+  LOG(INFO) << "parsing command line arguments";
+  base::CommandLine::Init(argc, argv);
+  LOG(INFO) << "initializing gtest";
+  SetUpTests(&argc, argv, true);
+  LOG(INFO) << "running unit tests";
+  int test_result = RUN_ALL_TESTS();
+  LOG(INFO) << "unittest return value: " << test_result;
+  return test_result;
+}
diff --git a/unittest_key.pem b/unittest_key.pem
new file mode 100644
index 0000000..224d3c3
--- /dev/null
+++ b/unittest_key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAx6hqS+Hbc7jN82ek+OBkHJNvlWXktyS2XCuQtGtnHjEV7Ou5
+hHNF96s5wEn8RDtrdLocm0a+SqXXf7KycFRTZxLdDbqWAMUlPXOaPI+PZLWkB7/K
+V7Cdj6vPGbeq7elu+aT/budhzTvq2stYrrAlcr/21tOVDPiWtfCdykh7FCXi6FaY
+HoNy5A6EKQLfLBuJoU/QoCufldmwlFaFDKl+Koos6R1QYJfCNZfgocrW1PIh+8t1
+JIvw6Izo8+fTme7nev7Ol2YZiMWJpRYKx8MgxW2UgTXlRpmQN56pc9LUsnVA8FND
+BN57+gMJj+XmdDmbtMpOsyXfSNEgnuwMPcEcmwIDAQABAoIBABPkwgKhlH4pUcwI
+7bUmlpMKVbnrFyjwbYMtjBOOCA5IEckzi56Y5cXRt8VjGdGqogBVcvg9ykQh1iER
+KxpqLI0+oev2RW/6NMW0uQ+DtmPwfVGQWJb4MBraoZ4MYOmnsrkJKbJhN6t9Zt86
+F7IANxsB6ZRqLJXIRywFt5MqOak+GAnQJ8C8eSQg70NhbEhSOrD8wrD6tfvgIqta
+XxhtlQWUAILIWetnWrJsalMqnreGn7vhc7+iihhMtXh1xNBMTA+gzpov/Cx21iH5
+DM9ppSA6HHDXrMhauryypIRrhjOUWRyDws/kIHgIW4TCbULOlxqsputQeTmdf0ti
+7lpwqAECgYEA7nNKkct3Vs51Ugk4GUPC4NOyYRPNc9UQAfHViB9gSDRacCo9Ax9J
+83hJGqDXlNGzELOnhzMn8jQMyF13eWzOsMozK6Fj3uW7DBvelg5bfgsZDUUO5WUF
+6BYbOheVqf12rIHR9BKBmCfLEKyxbKmw5bnB0uNo7IuBPBNuhPbvkgECgYEA1lo5
+XHWJpQnVl+JzXLHpXBK2nfnFAOtvzlTW+7gteeU12X2HcFASrzp7C1ULVV+i1Kcz
+tDFIA5yiFjEqmSJ/TsO8aqAhL5BXJjylCepQK7XkEOGCR8eQjlt7E4DulAsQbfpt
+k30HVVlIOFqLCWKSW8M3dy/Plodq/Gyq26rntpsCgYAzsNyGdIQfVkxKh2MY3v6c
+/Gdb8g4EwThiI4m1o4+ct3SvggiN57eBRx8Z3ao+QaM+yKNVhLpxH+VxfgmLUhIQ
+cxTarXbX+BcvTc9X2i7tSPyaStEq21aHdFtcoYY5Po/+X3ojHevoDyBPMhCYTMTj
+V/xzegbh2HAglNnNizZuAQKBgQCyOxEpBP5vgS7d/MgJklFEYrb/wjgBnMI5oSek
+5C7EBUdyUUM1qw7uLsUy1gL3eO7uvRxrvvJvNmU76KPP8vRCLNTVH9KYNv+P5qsg
+BHmm7rX1J11pi9Fx3TUIMZOu+0gs+ib0lOhtGjDH0tl680BZFohfDR0hv/XAcCbd
+Qk0q8wKBgQCGbURFFW/5RZUA77MmpY6jsEMw2gJmVtjO5IWZg9VPvLQQPgCr4Ho/
+bS2LIsT6pS+hrLOoz5KI0YueRS0jQYI+WkRqNf5wYNjxPql9632FiDLHO+Xv8PBe
+kHrPHy0GGT1igXScY4eemw4ZC1OSdZfkVn6Ui/JvBHrydJ2LrutMWQ==
+-----END RSA PRIVATE KEY-----
diff --git a/unittest_key2.pem b/unittest_key2.pem
new file mode 100644
index 0000000..d1f9a78
--- /dev/null
+++ b/unittest_key2.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqdM7gnd43cWfQzyrt16QDHE+T0yxg18OZO+9sc8jWpjC1zFt
+oMFokESiu9TLUp+KUC274f+DxIgAfSCO6U5DJFRPXUzve1vaXYfqljUBxc+JYcGd
+d6PCUl4Ap9f02DhkrGnf/rkHOCEhFNpm53dovZ9A6Q5KBfcg1HeQ088opVceQGtU
+mL+bONq5tLvNFLUU0Ru0Am4ADJ8Xmw4rpvqvmXJaFmVuf/Gx7+TOnaJvZTeOO9AJ
+K6q6V8E7+ZZiMIc5F4EOs5AX/lZfNO3RVgG2O7Dhzza+nOzJ3ZJGK5R4WwZduhn9
+QRYogYPB0c62878qXpf2Bn3NpUPi8Cf/RLMMmQIDAQABAoIBACyLUWKpP7S770hN
+k6TnUtVQps1aCn2w4y+qipEnCdjrlL+pIV43HNwqhJzL9gDYBAl/1XYz9TYJjkdD
+0Ph1JLtUufR5B5/NufsqeWeow6xFAX34sPr+oyvDqFxeEsTcFdv7cVt44OHiHrE/
+kBpKgdiq+vWmX9gsuBnCuuQzxC+Juo6nupwZXcpa/ow9lC4QsgKqcjaUGrXXy2t9
+Er+9aSl8NdTjK76BXQsDgNkDyJZwNN14xrdS8eFsS4twskaOEYI4hEM0g62NOjgd
+Po8Ap/MnPpGSGcAd3d3Fq8KgT1lpyMKedLFU+k0H+/Y4RBl7grz1XXvSTzGi3Qy6
+38F4eVkCgYEA4mo4iiXSfrov9QffsVM17O0t9hUsOJHnUsEScxWLDm4IzaObyTtv
+tWW33iQSeFu4Wsol0nzjqWo3BaqiRidRUd42yZ07LJvfUDxUX9xPaUPFRs25iwhZ
+6tKAVqGk7/CFrN+R44sIwbsSvbExMAyW6gnj93EWUmMWWYp02hLbN0sCgYEAwAQI
+awVoc56OCtRpfYtlAPD/VOP1mbNzRmVl/UyZ4XYmz6f/hEz63Bk5PhYSZftlmK/r
+nj4qnl7HZ8jrJgZn2e97rPNpk7KDVU1+csCgLWZBTOXl/o9tOTyjh9LoRAjKtBB7
+x6CkWyiyd94xIq5VbnXhvL3a4d4o6OxMWdG5aSsCgYEAo44z1afIzP7WkdzkPIZt
+l/8linR1A1BymBccqsHPN9dIyLP9X3puEc2u6uuH5CXtoLgSZmENXF577L38h0zz
+s34gebf4/RqEUMOj97OAMfxgz+rgs4yO19DEINCYAzPufJjsHEFdTAVFXn5Xl+wg
+QGRwp1ir1Uv64yffjYC9ls0CgYEAjvIxpiKniPNvwUYypmDgt5uyKetvCpaaabzQ
++YpOQJep+wuRYFfCpZotkDf0SHGoR8wnd23GYpIilvPvgyZfp9HuW2n2nhrWROnl
+Cd63IDUwxeOcni7+XA71mwb7HLMC3Jws2geQc8DPZAdIww3P0eT2QYGBcobmI8jO
+akuEYXMCgYAm79Kb/r+3Hew5oAS1Whw70DskVlOutSgNsDPfW9MtDcnETBcGep7A
+1jCL5jjdUYRonimVMFjh1K+UFzV/DQHkgNzjxz9Inbh02y67vL2X836dS9esOcbx
+uZhf+8rL+GnSNqYDqCEuP7qCIloDhguJq9NKyTB4yc59qIkY2zPAzQ==
+-----END RSA PRIVATE KEY-----
diff --git a/update_attempter.cc b/update_attempter.cc
new file mode 100644
index 0000000..a1b2f11
--- /dev/null
+++ b/update_attempter.cc
@@ -0,0 +1,1684 @@
+// Copyright (c) 2012 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/update_attempter.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include <glib.h>
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/certificate_checker.h"
+#include "update_engine/clock_interface.h"
+#include "update_engine/constants.h"
+#include "update_engine/dbus_service.h"
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/download_action.h"
+#include "update_engine/filesystem_verifier_action.h"
+#include "update_engine/glib_utils.h"
+#include "update_engine/hardware_interface.h"
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/metrics.h"
+#include "update_engine/multi_range_http_fetcher.h"
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/postinstall_runner_action.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using chromeos::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateCheckParams;
+using std::set;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+
+namespace {
+const int kMaxConsecutiveObeyProxyRequests = 20;
+
+const char kUpdateCompletedMarker[] =
+    "/var/run/update_engine_autoupdate_completed";
+
+// By default autest bypasses scattering. If we want to test scattering,
+// use kScheduledAUTestURLRequest. The URL used is same in both cases, but
+// different params are passed to CheckForUpdate().
+const char kAUTestURLRequest[] = "autest";
+const char kScheduledAUTestURLRequest[] = "autest-scheduled";
+}  // namespace
+
+const char* UpdateStatusToString(UpdateStatus status) {
+  switch (status) {
+    case UPDATE_STATUS_IDLE:
+      return update_engine::kUpdateStatusIdle;
+    case UPDATE_STATUS_CHECKING_FOR_UPDATE:
+      return update_engine::kUpdateStatusCheckingForUpdate;
+    case UPDATE_STATUS_UPDATE_AVAILABLE:
+      return update_engine::kUpdateStatusUpdateAvailable;
+    case UPDATE_STATUS_DOWNLOADING:
+      return update_engine::kUpdateStatusDownloading;
+    case UPDATE_STATUS_VERIFYING:
+      return update_engine::kUpdateStatusVerifying;
+    case UPDATE_STATUS_FINALIZING:
+      return update_engine::kUpdateStatusFinalizing;
+    case UPDATE_STATUS_UPDATED_NEED_REBOOT:
+      return update_engine::kUpdateStatusUpdatedNeedReboot;
+    case UPDATE_STATUS_REPORTING_ERROR_EVENT:
+      return update_engine::kUpdateStatusReportingErrorEvent;
+    case UPDATE_STATUS_ATTEMPTING_ROLLBACK:
+      return update_engine::kUpdateStatusAttemptingRollback;
+    case UPDATE_STATUS_DISABLED:
+      return update_engine::kUpdateStatusDisabled;
+    default:
+      return "unknown status";
+  }
+}
+
+// Turns a generic ErrorCode::kError to a generic error code specific
+// to |action| (e.g., ErrorCode::kFilesystemVerifierError). If |code| is
+// not ErrorCode::kError, or the action is not matched, returns |code|
+// unchanged.
+ErrorCode GetErrorCodeForAction(AbstractAction* action,
+                                     ErrorCode code) {
+  if (code != ErrorCode::kError)
+    return code;
+
+  const string type = action->Type();
+  if (type == OmahaRequestAction::StaticType())
+    return ErrorCode::kOmahaRequestError;
+  if (type == OmahaResponseHandlerAction::StaticType())
+    return ErrorCode::kOmahaResponseHandlerError;
+  if (type == FilesystemVerifierAction::StaticType())
+    return ErrorCode::kFilesystemVerifierError;
+  if (type == PostinstallRunnerAction::StaticType())
+    return ErrorCode::kPostinstallRunnerError;
+
+  return code;
+}
+
+UpdateAttempter::UpdateAttempter(SystemState* system_state,
+                                 DBusWrapperInterface* dbus_iface)
+    : UpdateAttempter(system_state, dbus_iface, kUpdateCompletedMarker) {}
+
+UpdateAttempter::UpdateAttempter(SystemState* system_state,
+                                 DBusWrapperInterface* dbus_iface,
+                                 const string& update_completed_marker)
+    : processor_(new ActionProcessor()),
+      system_state_(system_state),
+      dbus_iface_(dbus_iface),
+      chrome_proxy_resolver_(dbus_iface),
+      update_completed_marker_(update_completed_marker) {
+  if (!update_completed_marker_.empty() &&
+      utils::FileExists(update_completed_marker_.c_str())) {
+    status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT;
+  } else {
+    status_ = UPDATE_STATUS_IDLE;
+  }
+}
+
+UpdateAttempter::~UpdateAttempter() {
+  CleanupCpuSharesManagement();
+}
+
+void UpdateAttempter::Init() {
+  // Pulling from the SystemState can only be done after construction, since
+  // this is an aggregate of various objects (such as the UpdateAttempter),
+  // which requires them all to be constructed prior to it being used.
+  prefs_ = system_state_->prefs();
+  omaha_request_params_ = system_state_->request_params();
+}
+
+void UpdateAttempter::ScheduleUpdates() {
+  if (IsUpdateRunningOrScheduled())
+    return;
+
+  chromeos_update_manager::UpdateManager* const update_manager =
+      system_state_->update_manager();
+  CHECK(update_manager);
+  Callback<void(EvalStatus, const UpdateCheckParams&)> callback = Bind(
+      &UpdateAttempter::OnUpdateScheduled, base::Unretained(this));
+  // We limit the async policy request to a reasonably short time, to avoid a
+  // starvation due to a transient bug.
+  update_manager->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  waiting_for_scheduled_check_ = true;
+}
+
+bool UpdateAttempter::CheckAndReportDailyMetrics() {
+  int64_t stored_value;
+  Time now = system_state_->clock()->GetWallclockTime();
+  if (system_state_->prefs()->Exists(kPrefsDailyMetricsLastReportedAt) &&
+      system_state_->prefs()->GetInt64(kPrefsDailyMetricsLastReportedAt,
+                                       &stored_value)) {
+    Time last_reported_at = Time::FromInternalValue(stored_value);
+    TimeDelta time_reported_since = now - last_reported_at;
+    if (time_reported_since.InSeconds() < 0) {
+      LOG(WARNING) << "Last reported daily metrics "
+                   << utils::FormatTimeDelta(time_reported_since) << " ago "
+                   << "which is negative. Either the system clock is wrong or "
+                   << "the kPrefsDailyMetricsLastReportedAt state variable "
+                   << "is wrong.";
+      // In this case, report daily metrics to reset.
+    } else {
+      if (time_reported_since.InSeconds() < 24*60*60) {
+        LOG(INFO) << "Last reported daily metrics "
+                  << utils::FormatTimeDelta(time_reported_since) << " ago.";
+        return false;
+      }
+      LOG(INFO) << "Last reported daily metrics "
+                << utils::FormatTimeDelta(time_reported_since) << " ago, "
+                << "which is more than 24 hours ago.";
+    }
+  }
+
+  LOG(INFO) << "Reporting daily metrics.";
+  system_state_->prefs()->SetInt64(kPrefsDailyMetricsLastReportedAt,
+                                   now.ToInternalValue());
+
+  ReportOSAge();
+
+  return true;
+}
+
+void UpdateAttempter::ReportOSAge() {
+  struct stat sb;
+
+  if (system_state_ == nullptr)
+    return;
+
+  if (stat("/etc/lsb-release", &sb) != 0) {
+    PLOG(ERROR) << "Error getting file status for /etc/lsb-release "
+                << "(Note: this may happen in some unit tests)";
+    return;
+  }
+
+  Time lsb_release_timestamp = utils::TimeFromStructTimespec(&sb.st_ctim);
+  Time now = system_state_->clock()->GetWallclockTime();
+  TimeDelta age = now - lsb_release_timestamp;
+  if (age.InSeconds() < 0) {
+    LOG(ERROR) << "The OS age (" << utils::FormatTimeDelta(age)
+               << ") is negative. Maybe the clock is wrong? "
+               << "(Note: this may happen in some unit tests.)";
+    return;
+  }
+
+  string metric = "Installer.OSAgeDays";
+  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(age)
+            << " for metric " <<  metric;
+  system_state_->metrics_lib()->SendToUMA(
+       metric,
+       static_cast<int>(age.InDays()),
+       0,             // min: 0 days
+       6*30,          // max: 6 months (approx)
+       kNumDefaultUmaBuckets);
+
+  metrics::ReportDailyMetrics(system_state_, age);
+}
+
+void UpdateAttempter::Update(const string& app_version,
+                             const string& omaha_url,
+                             const string& target_channel,
+                             const string& target_version_prefix,
+                             bool obey_proxies,
+                             bool interactive) {
+  // This is normally called frequently enough so it's appropriate to use as a
+  // hook for reporting daily metrics.
+  // TODO(garnold) This should be hooked to a separate (reliable and consistent)
+  // timeout event.
+  CheckAndReportDailyMetrics();
+
+  // Notify of the new update attempt, clearing prior interactive requests.
+  if (forced_update_pending_callback_.get())
+    forced_update_pending_callback_->Run(false, false);
+
+  chrome_proxy_resolver_.Init();
+  fake_update_success_ = false;
+  if (status_ == UPDATE_STATUS_UPDATED_NEED_REBOOT) {
+    // Although we have applied an update, we still want to ping Omaha
+    // to ensure the number of active statistics is accurate.
+    //
+    // Also convey to the UpdateEngine.Check.Result metric that we're
+    // not performing an update check because of this.
+    LOG(INFO) << "Not updating b/c we already updated and we're waiting for "
+              << "reboot, we'll ping Omaha instead";
+    metrics::ReportUpdateCheckMetrics(system_state_,
+                                      metrics::CheckResult::kRebootPending,
+                                      metrics::CheckReaction::kUnset,
+                                      metrics::DownloadErrorCode::kUnset);
+    PingOmaha();
+    return;
+  }
+  if (status_ != UPDATE_STATUS_IDLE) {
+    // Update in progress. Do nothing
+    return;
+  }
+
+  if (!CalculateUpdateParams(app_version,
+                             omaha_url,
+                             target_channel,
+                             target_version_prefix,
+                             obey_proxies,
+                             interactive)) {
+    return;
+  }
+
+  BuildUpdateActions(interactive);
+
+  SetStatusAndNotify(UPDATE_STATUS_CHECKING_FOR_UPDATE);
+
+  // Update the last check time here; it may be re-updated when an Omaha
+  // response is received, but this will prevent us from repeatedly scheduling
+  // checks in the case where a response is not received.
+  UpdateLastCheckedTime();
+
+  // Just in case we didn't update boot flags yet, make sure they're updated
+  // before any update processing starts.
+  start_action_processor_ = true;
+  UpdateBootFlags();
+}
+
+void UpdateAttempter::RefreshDevicePolicy() {
+  // Lazy initialize the policy provider, or reload the latest policy data.
+  if (!policy_provider_.get())
+    policy_provider_.reset(new policy::PolicyProvider());
+  policy_provider_->Reload();
+
+  const policy::DevicePolicy* device_policy = nullptr;
+  if (policy_provider_->device_policy_is_loaded())
+    device_policy = &policy_provider_->GetDevicePolicy();
+
+  if (device_policy)
+    LOG(INFO) << "Device policies/settings present";
+  else
+    LOG(INFO) << "No device policies/settings present.";
+
+  system_state_->set_device_policy(device_policy);
+  system_state_->p2p_manager()->SetDevicePolicy(device_policy);
+}
+
+void UpdateAttempter::CalculateP2PParams(bool interactive) {
+  bool use_p2p_for_downloading = false;
+  bool use_p2p_for_sharing = false;
+
+  // Never use p2p for downloading in interactive checks unless the
+  // developer has opted in for it via a marker file.
+  //
+  // (Why would a developer want to opt in? If he's working on the
+  // update_engine or p2p codebases so he can actually test his
+  // code.).
+
+  if (system_state_ != nullptr) {
+    if (!system_state_->p2p_manager()->IsP2PEnabled()) {
+      LOG(INFO) << "p2p is not enabled - disallowing p2p for both"
+                << " downloading and sharing.";
+    } else {
+      // Allow p2p for sharing, even in interactive checks.
+      use_p2p_for_sharing = true;
+      if (!interactive) {
+        LOG(INFO) << "Non-interactive check - allowing p2p for downloading";
+        use_p2p_for_downloading = true;
+      } else {
+        LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+                  << "since this update attempt is interactive.";
+      }
+    }
+  }
+
+  PayloadStateInterface* const payload_state = system_state_->payload_state();
+  payload_state->SetUsingP2PForDownloading(use_p2p_for_downloading);
+  payload_state->SetUsingP2PForSharing(use_p2p_for_sharing);
+}
+
+bool UpdateAttempter::CalculateUpdateParams(const string& app_version,
+                                            const string& omaha_url,
+                                            const string& target_channel,
+                                            const string& target_version_prefix,
+                                            bool obey_proxies,
+                                            bool interactive) {
+  http_response_code_ = 0;
+  PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+  // Refresh the policy before computing all the update parameters.
+  RefreshDevicePolicy();
+
+  // Set the target version prefix, if provided.
+  if (!target_version_prefix.empty())
+    omaha_request_params_->set_target_version_prefix(target_version_prefix);
+
+  CalculateScatteringParams(interactive);
+
+  CalculateP2PParams(interactive);
+  if (payload_state->GetUsingP2PForDownloading() ||
+      payload_state->GetUsingP2PForSharing()) {
+    // OK, p2p is to be used - start it and perform housekeeping.
+    if (!StartP2PAndPerformHousekeeping()) {
+      // If this fails, disable p2p for this attempt
+      LOG(INFO) << "Forcibly disabling use of p2p since starting p2p or "
+                << "performing housekeeping failed.";
+      payload_state->SetUsingP2PForDownloading(false);
+      payload_state->SetUsingP2PForSharing(false);
+    }
+  }
+
+  if (!omaha_request_params_->Init(app_version,
+                                   omaha_url,
+                                   interactive)) {
+    LOG(ERROR) << "Unable to initialize Omaha request params.";
+    return false;
+  }
+
+  // Set the target channel, if one was provided.
+  if (target_channel.empty()) {
+    LOG(INFO) << "No target channel mandated by policy.";
+  } else {
+    LOG(INFO) << "Setting target channel as mandated: " << target_channel;
+    // Pass in false for powerwash_allowed until we add it to the policy
+    // protobuf.
+    omaha_request_params_->SetTargetChannel(target_channel, false);
+
+    // Since this is the beginning of a new attempt, update the download
+    // channel. The download channel won't be updated until the next attempt,
+    // even if target channel changes meanwhile, so that how we'll know if we
+    // should cancel the current download attempt if there's such a change in
+    // target channel.
+    omaha_request_params_->UpdateDownloadChannel();
+  }
+
+  LOG(INFO) << "target_version_prefix = "
+            << omaha_request_params_->target_version_prefix()
+            << ", scatter_factor_in_seconds = "
+            << utils::FormatSecs(scatter_factor_.InSeconds());
+
+  LOG(INFO) << "Wall Clock Based Wait Enabled = "
+            << omaha_request_params_->wall_clock_based_wait_enabled()
+            << ", Update Check Count Wait Enabled = "
+            << omaha_request_params_->update_check_count_wait_enabled()
+            << ", Waiting Period = " << utils::FormatSecs(
+               omaha_request_params_->waiting_period().InSeconds());
+
+  LOG(INFO) << "Use p2p For Downloading = "
+            << payload_state->GetUsingP2PForDownloading()
+            << ", Use p2p For Sharing = "
+            << payload_state->GetUsingP2PForSharing();
+
+  obeying_proxies_ = true;
+  if (obey_proxies || proxy_manual_checks_ == 0) {
+    LOG(INFO) << "forced to obey proxies";
+    // If forced to obey proxies, every 20th request will not use proxies
+    proxy_manual_checks_++;
+    LOG(INFO) << "proxy manual checks: " << proxy_manual_checks_;
+    if (proxy_manual_checks_ >= kMaxConsecutiveObeyProxyRequests) {
+      proxy_manual_checks_ = 0;
+      obeying_proxies_ = false;
+    }
+  } else if (base::RandInt(0, 4) == 0) {
+    obeying_proxies_ = false;
+  }
+  LOG_IF(INFO, !obeying_proxies_) << "To help ensure updates work, this update "
+      "check we are ignoring the proxy settings and using "
+      "direct connections.";
+
+  DisableDeltaUpdateIfNeeded();
+  return true;
+}
+
+void UpdateAttempter::CalculateScatteringParams(bool interactive) {
+  // Take a copy of the old scatter value before we update it, as
+  // we need to update the waiting period if this value changes.
+  TimeDelta old_scatter_factor = scatter_factor_;
+  const policy::DevicePolicy* device_policy = system_state_->device_policy();
+  if (device_policy) {
+    int64_t new_scatter_factor_in_secs = 0;
+    device_policy->GetScatterFactorInSeconds(&new_scatter_factor_in_secs);
+    if (new_scatter_factor_in_secs < 0)  // sanitize input, just in case.
+      new_scatter_factor_in_secs  = 0;
+    scatter_factor_ = TimeDelta::FromSeconds(new_scatter_factor_in_secs);
+  }
+
+  bool is_scatter_enabled = false;
+  if (scatter_factor_.InSeconds() == 0) {
+    LOG(INFO) << "Scattering disabled since scatter factor is set to 0";
+  } else if (interactive) {
+    LOG(INFO) << "Scattering disabled as this is an interactive update check";
+  } else if (!system_state_->hardware()->IsOOBEComplete(nullptr)) {
+    LOG(INFO) << "Scattering disabled since OOBE is not complete yet";
+  } else {
+    is_scatter_enabled = true;
+    LOG(INFO) << "Scattering is enabled";
+  }
+
+  if (is_scatter_enabled) {
+    // This means the scattering policy is turned on.
+    // Now check if we need to update the waiting period. The two cases
+    // in which we'd need to update the waiting period are:
+    // 1. First time in process or a scheduled check after a user-initiated one.
+    //    (omaha_request_params_->waiting_period will be zero in this case).
+    // 2. Admin has changed the scattering policy value.
+    //    (new scattering value will be different from old one in this case).
+    int64_t wait_period_in_secs = 0;
+    if (omaha_request_params_->waiting_period().InSeconds() == 0) {
+      // First case. Check if we have a suitable value to set for
+      // the waiting period.
+      if (prefs_->GetInt64(kPrefsWallClockWaitPeriod, &wait_period_in_secs) &&
+          wait_period_in_secs > 0 &&
+          wait_period_in_secs <= scatter_factor_.InSeconds()) {
+        // This means:
+        // 1. There's a persisted value for the waiting period available.
+        // 2. And that persisted value is still valid.
+        // So, in this case, we should reuse the persisted value instead of
+        // generating a new random value to improve the chances of a good
+        // distribution for scattering.
+        omaha_request_params_->set_waiting_period(
+          TimeDelta::FromSeconds(wait_period_in_secs));
+        LOG(INFO) << "Using persisted wall-clock waiting period: " <<
+            utils::FormatSecs(
+                omaha_request_params_->waiting_period().InSeconds());
+      } else {
+        // This means there's no persisted value for the waiting period
+        // available or its value is invalid given the new scatter_factor value.
+        // So, we should go ahead and regenerate a new value for the
+        // waiting period.
+        LOG(INFO) << "Persisted value not present or not valid ("
+                  << utils::FormatSecs(wait_period_in_secs)
+                  << ") for wall-clock waiting period.";
+        GenerateNewWaitingPeriod();
+      }
+    } else if (scatter_factor_ != old_scatter_factor) {
+      // This means there's already a waiting period value, but we detected
+      // a change in the scattering policy value. So, we should regenerate the
+      // waiting period to make sure it's within the bounds of the new scatter
+      // factor value.
+      GenerateNewWaitingPeriod();
+    } else {
+      // Neither the first time scattering is enabled nor the scattering value
+      // changed. Nothing to do.
+      LOG(INFO) << "Keeping current wall-clock waiting period: " <<
+          utils::FormatSecs(
+              omaha_request_params_->waiting_period().InSeconds());
+    }
+
+    // The invariant at this point is that omaha_request_params_->waiting_period
+    // is non-zero no matter which path we took above.
+    LOG_IF(ERROR, omaha_request_params_->waiting_period().InSeconds() == 0)
+        << "Waiting Period should NOT be zero at this point!!!";
+
+    // Since scattering is enabled, wall clock based wait will always be
+    // enabled.
+    omaha_request_params_->set_wall_clock_based_wait_enabled(true);
+
+    // If we don't have any issues in accessing the file system to update
+    // the update check count value, we'll turn that on as well.
+    bool decrement_succeeded = DecrementUpdateCheckCount();
+    omaha_request_params_->set_update_check_count_wait_enabled(
+      decrement_succeeded);
+  } else {
+    // This means the scattering feature is turned off or disabled for
+    // this particular update check. Make sure to disable
+    // all the knobs and artifacts so that we don't invoke any scattering
+    // related code.
+    omaha_request_params_->set_wall_clock_based_wait_enabled(false);
+    omaha_request_params_->set_update_check_count_wait_enabled(false);
+    omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(0));
+    prefs_->Delete(kPrefsWallClockWaitPeriod);
+    prefs_->Delete(kPrefsUpdateCheckCount);
+    // Don't delete the UpdateFirstSeenAt file as we don't want manual checks
+    // that result in no-updates (e.g. due to server side throttling) to
+    // cause update starvation by having the client generate a new
+    // UpdateFirstSeenAt for each scheduled check that follows a manual check.
+  }
+}
+
+void UpdateAttempter::GenerateNewWaitingPeriod() {
+  omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(
+      base::RandInt(1, scatter_factor_.InSeconds())));
+
+  LOG(INFO) << "Generated new wall-clock waiting period: " << utils::FormatSecs(
+                omaha_request_params_->waiting_period().InSeconds());
+
+  // Do a best-effort to persist this in all cases. Even if the persistence
+  // fails, we'll still be able to scatter based on our in-memory value.
+  // The persistence only helps in ensuring a good overall distribution
+  // across multiple devices if they tend to reboot too often.
+  system_state_->payload_state()->SetScatteringWaitPeriod(
+      omaha_request_params_->waiting_period());
+}
+
+void UpdateAttempter::BuildPostInstallActions(
+    InstallPlanAction* previous_action) {
+  shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
+        new PostinstallRunnerAction());
+  actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+  BondActions(previous_action,
+              postinstall_runner_action.get());
+}
+
+void UpdateAttempter::BuildUpdateActions(bool interactive) {
+  CHECK(!processor_->IsRunning());
+  processor_->set_delegate(this);
+
+  // Actions:
+  LibcurlHttpFetcher* update_check_fetcher =
+      new LibcurlHttpFetcher(GetProxyResolver(), system_state_);
+  // Try harder to connect to the network, esp when not interactive.
+  // See comment in libcurl_http_fetcher.cc.
+  update_check_fetcher->set_no_network_max_retries(interactive ? 1 : 3);
+  update_check_fetcher->set_check_certificate(CertificateChecker::kUpdate);
+  shared_ptr<OmahaRequestAction> update_check_action(
+      new OmahaRequestAction(system_state_,
+                             nullptr,
+                             update_check_fetcher,  // passes ownership
+                             false));
+  shared_ptr<OmahaResponseHandlerAction> response_handler_action(
+      new OmahaResponseHandlerAction(system_state_));
+  shared_ptr<FilesystemVerifierAction> src_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_,
+                                   PartitionType::kSourceRootfs));
+  shared_ptr<FilesystemVerifierAction> src_kernel_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_,
+                                   PartitionType::kSourceKernel));
+
+  shared_ptr<OmahaRequestAction> download_started_action(
+      new OmahaRequestAction(system_state_,
+                             new OmahaEvent(
+                                 OmahaEvent::kTypeUpdateDownloadStarted),
+                             new LibcurlHttpFetcher(GetProxyResolver(),
+                                                    system_state_),
+                             false));
+  LibcurlHttpFetcher* download_fetcher =
+      new LibcurlHttpFetcher(GetProxyResolver(), system_state_);
+  download_fetcher->set_check_certificate(CertificateChecker::kDownload);
+  shared_ptr<DownloadAction> download_action(
+      new DownloadAction(prefs_,
+                         system_state_,
+                         new MultiRangeHttpFetcher(
+                             download_fetcher)));  // passes ownership
+  shared_ptr<OmahaRequestAction> download_finished_action(
+      new OmahaRequestAction(system_state_,
+                             new OmahaEvent(
+                                 OmahaEvent::kTypeUpdateDownloadFinished),
+                             new LibcurlHttpFetcher(GetProxyResolver(),
+                                                    system_state_),
+                             false));
+  shared_ptr<FilesystemVerifierAction> dst_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_, PartitionType::kRootfs));
+  shared_ptr<FilesystemVerifierAction> dst_kernel_filesystem_verifier_action(
+      new FilesystemVerifierAction(system_state_, PartitionType::kKernel));
+  shared_ptr<OmahaRequestAction> update_complete_action(
+      new OmahaRequestAction(system_state_,
+                             new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+                             new LibcurlHttpFetcher(GetProxyResolver(),
+                                                    system_state_),
+                             false));
+
+  download_action->set_delegate(this);
+  response_handler_action_ = response_handler_action;
+  download_action_ = download_action;
+
+  actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
+  actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      src_filesystem_verifier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      src_kernel_filesystem_verifier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_started_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_action));
+  actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      dst_filesystem_verifier_action));
+  actions_.push_back(shared_ptr<AbstractAction>(
+      dst_kernel_filesystem_verifier_action));
+
+  // Bond them together. We have to use the leaf-types when calling
+  // BondActions().
+  BondActions(update_check_action.get(),
+              response_handler_action.get());
+  BondActions(response_handler_action.get(),
+              src_filesystem_verifier_action.get());
+  BondActions(src_filesystem_verifier_action.get(),
+              src_kernel_filesystem_verifier_action.get());
+  BondActions(src_kernel_filesystem_verifier_action.get(),
+              download_action.get());
+  BondActions(download_action.get(),
+              dst_filesystem_verifier_action.get());
+  BondActions(dst_filesystem_verifier_action.get(),
+              dst_kernel_filesystem_verifier_action.get());
+
+  BuildPostInstallActions(dst_kernel_filesystem_verifier_action.get());
+
+  actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
+
+  // Enqueue the actions
+  for (const shared_ptr<AbstractAction>& action : actions_) {
+    processor_->EnqueueAction(action.get());
+  }
+}
+
+bool UpdateAttempter::Rollback(bool powerwash) {
+  if (!CanRollback()) {
+    return false;
+  }
+
+  // Extra check for enterprise-enrolled devices since they don't support
+  // powerwash.
+  if (powerwash) {
+    // Enterprise-enrolled devices have an empty owner in their device policy.
+    string owner;
+    RefreshDevicePolicy();
+    const policy::DevicePolicy* device_policy = system_state_->device_policy();
+    if (device_policy && (!device_policy->GetOwner(&owner) || owner.empty())) {
+      LOG(ERROR) << "Enterprise device detected. "
+                 << "Cannot perform a powerwash for enterprise devices.";
+      return false;
+    }
+  }
+
+  processor_->set_delegate(this);
+
+  // Initialize the default request params.
+  if (!omaha_request_params_->Init("", "", true)) {
+    LOG(ERROR) << "Unable to initialize Omaha request params.";
+    return false;
+  }
+
+  LOG(INFO) << "Setting rollback options.";
+  InstallPlan install_plan;
+
+  TEST_AND_RETURN_FALSE(utils::GetInstallDev(
+      system_state_->hardware()->BootDevice(),
+      &install_plan.install_path));
+
+  install_plan.kernel_install_path =
+      utils::KernelDeviceOfBootDevice(install_plan.install_path);
+  install_plan.source_path = system_state_->hardware()->BootDevice();
+  install_plan.kernel_source_path =
+      utils::KernelDeviceOfBootDevice(install_plan.source_path);
+  install_plan.powerwash_required = powerwash;
+
+  LOG(INFO) << "Using this install plan:";
+  install_plan.Dump();
+
+  shared_ptr<InstallPlanAction> install_plan_action(
+      new InstallPlanAction(install_plan));
+  actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
+
+  BuildPostInstallActions(install_plan_action.get());
+
+  // Enqueue the actions
+  for (const shared_ptr<AbstractAction>& action : actions_) {
+    processor_->EnqueueAction(action.get());
+  }
+
+  // Update the payload state for Rollback.
+  system_state_->payload_state()->Rollback();
+
+  SetStatusAndNotify(UPDATE_STATUS_ATTEMPTING_ROLLBACK);
+
+  // Just in case we didn't update boot flags yet, make sure they're updated
+  // before any update processing starts. This also schedules the start of the
+  // actions we just posted.
+  start_action_processor_ = true;
+  UpdateBootFlags();
+  return true;
+}
+
+bool UpdateAttempter::CanRollback() const {
+  // We can only rollback if the update_engine isn't busy and we have a valid
+  // rollback partition.
+  return (status_ == UPDATE_STATUS_IDLE && !GetRollbackPartition().empty());
+}
+
+string UpdateAttempter::GetRollbackPartition() const {
+  vector<string> kernel_devices =
+      system_state_->hardware()->GetKernelDevices();
+
+  string boot_kernel_device =
+      system_state_->hardware()->BootKernelDevice();
+
+  LOG(INFO) << "UpdateAttempter::GetRollbackPartition";
+  for (const auto& name : kernel_devices)
+    LOG(INFO) << "  Available kernel device = " << name;
+  LOG(INFO) << "  Boot kernel device =      " << boot_kernel_device;
+
+  auto current = std::find(kernel_devices.begin(), kernel_devices.end(),
+                           boot_kernel_device);
+
+  if (current == kernel_devices.end()) {
+    LOG(ERROR) << "Unable to find the boot kernel device in the list of "
+               << "available devices";
+    return string();
+  }
+
+  for (string const& device_name : kernel_devices) {
+    if (device_name != *current) {
+      bool bootable = false;
+      if (system_state_->hardware()->IsKernelBootable(device_name, &bootable) &&
+          bootable) {
+        return device_name;
+      }
+    }
+  }
+
+  return string();
+}
+
+vector<std::pair<string, bool>>
+    UpdateAttempter::GetKernelDevices() const {
+  vector<string> kernel_devices =
+    system_state_->hardware()->GetKernelDevices();
+
+  string boot_kernel_device =
+    system_state_->hardware()->BootKernelDevice();
+
+  vector<std::pair<string, bool>> info_list;
+  info_list.reserve(kernel_devices.size());
+
+  for (string device_name : kernel_devices) {
+    bool bootable = false;
+    system_state_->hardware()->IsKernelBootable(device_name, &bootable);
+    // Add '*' to the name of the partition we booted from.
+    if (device_name == boot_kernel_device)
+      device_name += '*';
+    info_list.emplace_back(device_name, bootable);
+  }
+
+  return info_list;
+}
+
+void UpdateAttempter::CheckForUpdate(const string& app_version,
+                                     const string& omaha_url,
+                                     bool interactive) {
+  LOG(INFO) << "Forced update check requested.";
+  forced_app_version_.clear();
+  forced_omaha_url_.clear();
+
+  // Certain conditions must be met to allow setting custom version and update
+  // server URLs. However, kScheduledAUTestURLRequest and kAUTestURLRequest are
+  // always allowed regardless of device state.
+  if (IsAnyUpdateSourceAllowed()) {
+    forced_app_version_ = app_version;
+    forced_omaha_url_ = omaha_url;
+  }
+  if (omaha_url == kScheduledAUTestURLRequest) {
+    forced_omaha_url_ = chromeos_update_engine::kAUTestOmahaUrl;
+    // Pretend that it's not user-initiated even though it is,
+    // so as to test scattering logic, etc. which get kicked off
+    // only in scheduled update checks.
+    interactive = false;
+  } else if (omaha_url == kAUTestURLRequest) {
+    forced_omaha_url_ = chromeos_update_engine::kAUTestOmahaUrl;
+  }
+
+  if (forced_update_pending_callback_.get()) {
+    // Make sure that a scheduling request is made prior to calling the forced
+    // update pending callback.
+    ScheduleUpdates();
+    forced_update_pending_callback_->Run(true, interactive);
+  }
+}
+
+bool UpdateAttempter::RebootIfNeeded() {
+  if (status_ != UPDATE_STATUS_UPDATED_NEED_REBOOT) {
+    LOG(INFO) << "Reboot requested, but status is "
+              << UpdateStatusToString(status_) << ", so not rebooting.";
+    return false;
+  }
+
+  if (USE_POWER_MANAGEMENT && RequestPowerManagerReboot())
+    return true;
+
+  return RebootDirectly();
+}
+
+void UpdateAttempter::WriteUpdateCompletedMarker() {
+  if (update_completed_marker_.empty())
+    return;
+
+  int64_t value = system_state_->clock()->GetBootTime().ToInternalValue();
+  string contents = StringPrintf("%" PRIi64, value);
+
+  utils::WriteFile(update_completed_marker_.c_str(),
+                   contents.c_str(),
+                   contents.length());
+}
+
+bool UpdateAttempter::RequestPowerManagerReboot() {
+  GError* error = nullptr;
+  DBusGConnection* bus = dbus_iface_->BusGet(DBUS_BUS_SYSTEM, &error);
+  if (!bus) {
+    LOG(ERROR) << "Failed to get system bus: "
+               << utils::GetAndFreeGError(&error);
+    return false;
+  }
+
+  LOG(INFO) << "Calling " << power_manager::kPowerManagerInterface << "."
+            << power_manager::kRequestRestartMethod;
+  DBusGProxy* proxy = dbus_iface_->ProxyNewForName(
+      bus,
+      power_manager::kPowerManagerServiceName,
+      power_manager::kPowerManagerServicePath,
+      power_manager::kPowerManagerInterface);
+  const gboolean success = dbus_iface_->ProxyCall_1_0(
+      proxy,
+      power_manager::kRequestRestartMethod,
+      &error,
+      power_manager::REQUEST_RESTART_FOR_UPDATE);
+  dbus_iface_->ProxyUnref(proxy);
+
+  if (!success) {
+    LOG(ERROR) << "Failed to call " << power_manager::kRequestRestartMethod
+               << ": " << utils::GetAndFreeGError(&error);
+    return false;
+  }
+
+  return true;
+}
+
+bool UpdateAttempter::RebootDirectly() {
+  vector<string> command;
+  command.push_back("/sbin/shutdown");
+  command.push_back("-r");
+  command.push_back("now");
+  LOG(INFO) << "Running \"" << JoinString(command, ' ') << "\"";
+  int rc = 0;
+  Subprocess::SynchronousExec(command, &rc, nullptr);
+  return rc == 0;
+}
+
+void UpdateAttempter::OnUpdateScheduled(EvalStatus status,
+                                        const UpdateCheckParams& params) {
+  waiting_for_scheduled_check_ = false;
+
+  if (status == EvalStatus::kSucceeded) {
+    if (!params.updates_enabled) {
+      LOG(WARNING) << "Updates permanently disabled.";
+      // Signal disabled status, then switch right back to idle. This is
+      // necessary for ensuring that observers waiting for a signal change will
+      // actually notice one on subsequent calls. Note that we don't need to
+      // re-schedule a check in this case as updates are permanently disabled;
+      // further (forced) checks may still initiate a scheduling call.
+      SetStatusAndNotify(UPDATE_STATUS_DISABLED);
+      SetStatusAndNotify(UPDATE_STATUS_IDLE);
+      return;
+    }
+
+    LOG(INFO) << "Running "
+              << (params.is_interactive ? "interactive" : "periodic")
+              << " update.";
+
+    string app_version, omaha_url;
+    if (params.is_interactive) {
+      app_version = forced_app_version_;
+      omaha_url = forced_omaha_url_;
+    } else {
+      // Flush previously generated UMA reports before periodic updates.
+      CertificateChecker::FlushReport();
+    }
+
+    Update(app_version, omaha_url, params.target_channel,
+           params.target_version_prefix, false, params.is_interactive);
+  } else {
+    LOG(WARNING)
+        << "Update check scheduling failed (possibly timed out); retrying.";
+    ScheduleUpdates();
+  }
+
+  // This check ensures that future update checks will be or are already
+  // scheduled. The check should never fail. A check failure means that there's
+  // a bug that will most likely prevent further automatic update checks. It
+  // seems better to crash in such cases and restart the update_engine daemon
+  // into, hopefully, a known good state.
+  CHECK(IsUpdateRunningOrScheduled());
+}
+
+void UpdateAttempter::UpdateLastCheckedTime() {
+  last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT();
+}
+
+// Delegate methods:
+void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
+                                     ErrorCode code) {
+  LOG(INFO) << "Processing Done.";
+  actions_.clear();
+
+  // Reset cpu shares back to normal.
+  CleanupCpuSharesManagement();
+
+  if (status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) {
+    LOG(INFO) << "Error event sent.";
+
+    // Inform scheduler of new status;
+    SetStatusAndNotify(UPDATE_STATUS_IDLE);
+    ScheduleUpdates();
+
+    if (!fake_update_success_) {
+      return;
+    }
+    LOG(INFO) << "Booted from FW B and tried to install new firmware, "
+        "so requesting reboot from user.";
+  }
+
+  if (code == ErrorCode::kSuccess) {
+    WriteUpdateCompletedMarker();
+    prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
+    prefs_->SetString(kPrefsPreviousVersion,
+                      omaha_request_params_->app_version());
+    DeltaPerformer::ResetUpdateProgress(prefs_, false);
+
+    system_state_->payload_state()->UpdateSucceeded();
+
+    // Since we're done with scattering fully at this point, this is the
+    // safest point delete the state files, as we're sure that the status is
+    // set to reboot (which means no more updates will be applied until reboot)
+    // This deletion is required for correctness as we want the next update
+    // check to re-create a new random number for the update check count.
+    // Similarly, we also delete the wall-clock-wait period that was persisted
+    // so that we start with a new random value for the next update check
+    // after reboot so that the same device is not favored or punished in any
+    // way.
+    prefs_->Delete(kPrefsUpdateCheckCount);
+    system_state_->payload_state()->SetScatteringWaitPeriod(TimeDelta());
+    prefs_->Delete(kPrefsUpdateFirstSeenAt);
+
+    SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
+    ScheduleUpdates();
+    LOG(INFO) << "Update successfully applied, waiting to reboot.";
+
+    // This pointer is null during rollback operations, and the stats
+    // don't make much sense then anyway.
+    if (response_handler_action_) {
+      const InstallPlan& install_plan =
+          response_handler_action_->install_plan();
+
+      // Generate an unique payload identifier.
+      const string target_version_uid =
+          install_plan.payload_hash + ":" + install_plan.metadata_signature;
+
+      // Expect to reboot into the new version to send the proper metric during
+      // next boot.
+      system_state_->payload_state()->ExpectRebootInNewVersion(
+          target_version_uid);
+
+      // Also report the success code so that the percentiles can be
+      // interpreted properly for the remaining error codes in UMA.
+      utils::SendErrorCodeToUma(system_state_, code);
+    } else {
+      // If we just finished a rollback, then we expect to have no Omaha
+      // response. Otherwise, it's an error.
+      if (system_state_->payload_state()->GetRollbackVersion().empty()) {
+        LOG(ERROR) << "Can't send metrics because expected "
+            "response_handler_action_ missing.";
+      }
+    }
+    return;
+  }
+
+  if (ScheduleErrorEventAction()) {
+    return;
+  }
+  LOG(INFO) << "No update.";
+  SetStatusAndNotify(UPDATE_STATUS_IDLE);
+  ScheduleUpdates();
+}
+
+void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
+  // Reset cpu shares back to normal.
+  CleanupCpuSharesManagement();
+  download_progress_ = 0.0;
+  SetStatusAndNotify(UPDATE_STATUS_IDLE);
+  ScheduleUpdates();
+  actions_.clear();
+  error_event_.reset(nullptr);
+}
+
+// Called whenever an action has finished processing, either successfully
+// or otherwise.
+void UpdateAttempter::ActionCompleted(ActionProcessor* processor,
+                                      AbstractAction* action,
+                                      ErrorCode code) {
+  // Reset download progress regardless of whether or not the download
+  // action succeeded. Also, get the response code from HTTP request
+  // actions (update download as well as the initial update check
+  // actions).
+  const string type = action->Type();
+  if (type == DownloadAction::StaticType()) {
+    download_progress_ = 0.0;
+    DownloadAction* download_action = static_cast<DownloadAction*>(action);
+    http_response_code_ = download_action->GetHTTPResponseCode();
+  } else if (type == OmahaRequestAction::StaticType()) {
+    OmahaRequestAction* omaha_request_action =
+        static_cast<OmahaRequestAction*>(action);
+    // If the request is not an event, then it's the update-check.
+    if (!omaha_request_action->IsEvent()) {
+      http_response_code_ = omaha_request_action->GetHTTPResponseCode();
+
+      // Record the number of consecutive failed update checks.
+      if (http_response_code_ == kHttpResponseInternalServerError ||
+          http_response_code_ == kHttpResponseServiceUnavailable) {
+        consecutive_failed_update_checks_++;
+      } else {
+        consecutive_failed_update_checks_ = 0;
+      }
+
+      // Store the server-dictated poll interval, if any.
+      server_dictated_poll_interval_ =
+          std::max(0, omaha_request_action->GetOutputObject().poll_interval);
+    }
+  }
+  if (code != ErrorCode::kSuccess) {
+    // If the current state is at or past the download phase, count the failure
+    // in case a switch to full update becomes necessary. Ignore network
+    // transfer timeouts and failures.
+    if (status_ >= UPDATE_STATUS_DOWNLOADING &&
+        code != ErrorCode::kDownloadTransferError) {
+      MarkDeltaUpdateFailure();
+    }
+    // On failure, schedule an error event to be sent to Omaha.
+    CreatePendingErrorEvent(action, code);
+    return;
+  }
+  // Find out which action completed.
+  if (type == OmahaResponseHandlerAction::StaticType()) {
+    // Note that the status will be updated to DOWNLOADING when some bytes get
+    // actually downloaded from the server and the BytesReceived callback is
+    // invoked. This avoids notifying the user that a download has started in
+    // cases when the server and the client are unable to initiate the download.
+    CHECK(action == response_handler_action_.get());
+    const InstallPlan& plan = response_handler_action_->install_plan();
+    UpdateLastCheckedTime();
+    new_version_ = plan.version;
+    new_payload_size_ = plan.payload_size;
+    SetupDownload();
+    SetupCpuSharesManagement();
+    SetStatusAndNotify(UPDATE_STATUS_UPDATE_AVAILABLE);
+  } else if (type == DownloadAction::StaticType()) {
+    SetStatusAndNotify(UPDATE_STATUS_FINALIZING);
+  }
+}
+
+void UpdateAttempter::SetDownloadStatus(bool active) {
+  download_active_ = active;
+  LOG(INFO) << "Download status: " << (active ? "active" : "inactive");
+}
+
+void UpdateAttempter::BytesReceived(uint64_t bytes_received, uint64_t total) {
+  if (!download_active_) {
+    LOG(ERROR) << "BytesReceived called while not downloading.";
+    return;
+  }
+  double progress = static_cast<double>(bytes_received) /
+      static_cast<double>(total);
+  // Self throttle based on progress. Also send notifications if
+  // progress is too slow.
+  const double kDeltaPercent = 0.01;  // 1%
+  if (status_ != UPDATE_STATUS_DOWNLOADING ||
+      bytes_received == total ||
+      progress - download_progress_ >= kDeltaPercent ||
+      TimeTicks::Now() - last_notify_time_ >= TimeDelta::FromSeconds(10)) {
+    download_progress_ = progress;
+    SetStatusAndNotify(UPDATE_STATUS_DOWNLOADING);
+  }
+}
+
+bool UpdateAttempter::ResetStatus() {
+  LOG(INFO) << "Attempting to reset state from "
+            << UpdateStatusToString(status_) << " to UPDATE_STATUS_IDLE";
+
+  switch (status_) {
+    case UPDATE_STATUS_IDLE:
+      // no-op.
+      return true;
+
+    case UPDATE_STATUS_UPDATED_NEED_REBOOT:  {
+      bool ret_value = true;
+      status_ = UPDATE_STATUS_IDLE;
+      LOG(INFO) << "Reset Successful";
+
+      // Remove the reboot marker so that if the machine is rebooted
+      // after resetting to idle state, it doesn't go back to
+      // UPDATE_STATUS_UPDATED_NEED_REBOOT state.
+      if (!update_completed_marker_.empty()) {
+        if (!base::DeleteFile(base::FilePath(update_completed_marker_), false))
+          ret_value = false;
+      }
+
+      // Notify the PayloadState that the successful payload was canceled.
+      system_state_->payload_state()->ResetUpdateStatus();
+
+      return ret_value;
+    }
+
+    default:
+      LOG(ERROR) << "Reset not allowed in this state.";
+      return false;
+  }
+}
+
+bool UpdateAttempter::GetStatus(int64_t* last_checked_time,
+                                double* progress,
+                                string* current_operation,
+                                string* new_version,
+                                int64_t* new_payload_size) {
+  *last_checked_time = last_checked_time_;
+  *progress = download_progress_;
+  *current_operation = UpdateStatusToString(status_);
+  *new_version = new_version_;
+  *new_payload_size = new_payload_size_;
+  return true;
+}
+
+void UpdateAttempter::UpdateBootFlags() {
+  if (update_boot_flags_running_) {
+    LOG(INFO) << "Update boot flags running, nothing to do.";
+    return;
+  }
+  if (updated_boot_flags_) {
+    LOG(INFO) << "Already updated boot flags. Skipping.";
+    if (start_action_processor_) {
+      ScheduleProcessingStart();
+    }
+    return;
+  }
+  // This is purely best effort. Failures should be logged by Subprocess. Run
+  // the script asynchronously to avoid blocking the event loop regardless of
+  // the script runtime.
+  update_boot_flags_running_ = true;
+  LOG(INFO) << "Updating boot flags...";
+  vector<string> cmd{set_good_kernel_cmd_};
+  if (!Subprocess::Get().Exec(cmd, StaticCompleteUpdateBootFlags, this)) {
+    CompleteUpdateBootFlags(1);
+  }
+}
+
+void UpdateAttempter::CompleteUpdateBootFlags(int return_code) {
+  update_boot_flags_running_ = false;
+  updated_boot_flags_ = true;
+  if (start_action_processor_) {
+    ScheduleProcessingStart();
+  }
+}
+
+void UpdateAttempter::StaticCompleteUpdateBootFlags(
+    int return_code,
+    const string& output,
+    void* p) {
+  reinterpret_cast<UpdateAttempter*>(p)->CompleteUpdateBootFlags(return_code);
+}
+
+void UpdateAttempter::BroadcastStatus() {
+  if (!dbus_service_) {
+    return;
+  }
+  last_notify_time_ = TimeTicks::Now();
+  update_engine_service_emit_status_update(
+      dbus_service_,
+      last_checked_time_,
+      download_progress_,
+      UpdateStatusToString(status_),
+      new_version_.c_str(),
+      new_payload_size_);
+}
+
+uint32_t UpdateAttempter::GetErrorCodeFlags()  {
+  uint32_t flags = 0;
+
+  if (!system_state_->hardware()->IsNormalBootMode())
+    flags |= static_cast<uint32_t>(ErrorCode::kDevModeFlag);
+
+  if (response_handler_action_.get() &&
+      response_handler_action_->install_plan().is_resume)
+    flags |= static_cast<uint32_t>(ErrorCode::kResumedFlag);
+
+  if (!system_state_->hardware()->IsOfficialBuild())
+    flags |= static_cast<uint32_t>(ErrorCode::kTestImageFlag);
+
+  if (omaha_request_params_->update_url() != kProductionOmahaUrl)
+    flags |= static_cast<uint32_t>(ErrorCode::kTestOmahaUrlFlag);
+
+  return flags;
+}
+
+bool UpdateAttempter::ShouldCancel(ErrorCode* cancel_reason) {
+  // Check if the channel we're attempting to update to is the same as the
+  // target channel currently chosen by the user.
+  OmahaRequestParams* params = system_state_->request_params();
+  if (params->download_channel() != params->target_channel()) {
+    LOG(ERROR) << "Aborting download as target channel: "
+               << params->target_channel()
+               << " is different from the download channel: "
+               << params->download_channel();
+    *cancel_reason = ErrorCode::kUpdateCanceledByChannelChange;
+    return true;
+  }
+
+  return false;
+}
+
+void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) {
+  status_ = status;
+  BroadcastStatus();
+}
+
+void UpdateAttempter::CreatePendingErrorEvent(AbstractAction* action,
+                                              ErrorCode code) {
+  if (error_event_.get()) {
+    // This shouldn't really happen.
+    LOG(WARNING) << "There's already an existing pending error event.";
+    return;
+  }
+
+  // For now assume that a generic Omaha response action failure means that
+  // there's no update so don't send an event. Also, double check that the
+  // failure has not occurred while sending an error event -- in which case
+  // don't schedule another. This shouldn't really happen but just in case...
+  if ((action->Type() == OmahaResponseHandlerAction::StaticType() &&
+       code == ErrorCode::kError) ||
+      status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) {
+    return;
+  }
+
+  // Classify the code to generate the appropriate result so that
+  // the Borgmon charts show up the results correctly.
+  // Do this before calling GetErrorCodeForAction which could potentially
+  // augment the bit representation of code and thus cause no matches for
+  // the switch cases below.
+  OmahaEvent::Result event_result;
+  switch (code) {
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+      event_result = OmahaEvent::kResultUpdateDeferred;
+      break;
+    default:
+      event_result = OmahaEvent::kResultError;
+      break;
+  }
+
+  code = GetErrorCodeForAction(action, code);
+  fake_update_success_ = code == ErrorCode::kPostinstallBootedFromFirmwareB;
+
+  // Compute the final error code with all the bit flags to be sent to Omaha.
+  code = static_cast<ErrorCode>(
+      static_cast<uint32_t>(code) | GetErrorCodeFlags());
+  error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete,
+                                    event_result,
+                                    code));
+}
+
+bool UpdateAttempter::ScheduleErrorEventAction() {
+  if (error_event_.get() == nullptr)
+    return false;
+
+  LOG(ERROR) << "Update failed.";
+  system_state_->payload_state()->UpdateFailed(error_event_->error_code);
+
+  // Send it to Uma.
+  LOG(INFO) << "Reporting the error event";
+  utils::SendErrorCodeToUma(system_state_, error_event_->error_code);
+
+  // Send it to Omaha.
+  shared_ptr<OmahaRequestAction> error_event_action(
+      new OmahaRequestAction(system_state_,
+                             error_event_.release(),  // Pass ownership.
+                             new LibcurlHttpFetcher(GetProxyResolver(),
+                                                    system_state_),
+                             false));
+  actions_.push_back(shared_ptr<AbstractAction>(error_event_action));
+  processor_->EnqueueAction(error_event_action.get());
+  SetStatusAndNotify(UPDATE_STATUS_REPORTING_ERROR_EVENT);
+  processor_->StartProcessing();
+  return true;
+}
+
+void UpdateAttempter::SetCpuShares(utils::CpuShares shares) {
+  if (shares_ == shares) {
+    return;
+  }
+  if (utils::SetCpuShares(shares)) {
+    shares_ = shares;
+    LOG(INFO) << "CPU shares = " << shares_;
+  }
+}
+
+void UpdateAttempter::SetupCpuSharesManagement() {
+  if (manage_shares_id_ != MessageLoop::kTaskIdNull) {
+    LOG(ERROR) << "Cpu shares timeout source hasn't been destroyed.";
+    CleanupCpuSharesManagement();
+  }
+  const int kCpuSharesTimeout = 2 * 60 * 60;  // 2 hours
+  manage_shares_id_ = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      Bind(&UpdateAttempter::ManageCpuSharesCallback, base::Unretained(this)),
+      TimeDelta::FromSeconds(kCpuSharesTimeout));
+  SetCpuShares(utils::kCpuSharesLow);
+}
+
+void UpdateAttempter::CleanupCpuSharesManagement() {
+  if (manage_shares_id_ != MessageLoop::kTaskIdNull) {
+    // The UpdateAttempter is instantiated by default by the FakeSystemState,
+    // even when it is not used. We check the manage_shares_id_ before calling
+    // the MessageLoop::current() since the unit test using a FakeSystemState
+    // may have not define a MessageLoop for the current thread.
+    MessageLoop::current()->CancelTask(manage_shares_id_);
+    manage_shares_id_ = MessageLoop::kTaskIdNull;
+  }
+  SetCpuShares(utils::kCpuSharesNormal);
+}
+
+void UpdateAttempter::ScheduleProcessingStart() {
+  LOG(INFO) << "Scheduling an action processor start.";
+  start_action_processor_ = false;
+  MessageLoop::current()->PostTask(
+      FROM_HERE,
+      Bind([this] { this->processor_->StartProcessing(); }));
+}
+
+void UpdateAttempter::ManageCpuSharesCallback() {
+  SetCpuShares(utils::kCpuSharesNormal);
+  manage_shares_id_ = MessageLoop::kTaskIdNull;
+}
+
+void UpdateAttempter::DisableDeltaUpdateIfNeeded() {
+  int64_t delta_failures;
+  if (omaha_request_params_->delta_okay() &&
+      prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) &&
+      delta_failures >= kMaxDeltaUpdateFailures) {
+    LOG(WARNING) << "Too many delta update failures, forcing full update.";
+    omaha_request_params_->set_delta_okay(false);
+  }
+}
+
+void UpdateAttempter::MarkDeltaUpdateFailure() {
+  // Don't try to resume a failed delta update.
+  DeltaPerformer::ResetUpdateProgress(prefs_, false);
+  int64_t delta_failures;
+  if (!prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) ||
+      delta_failures < 0) {
+    delta_failures = 0;
+  }
+  prefs_->SetInt64(kPrefsDeltaUpdateFailures, ++delta_failures);
+}
+
+void UpdateAttempter::SetupDownload() {
+  MultiRangeHttpFetcher* fetcher =
+      static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
+  fetcher->ClearRanges();
+  if (response_handler_action_->install_plan().is_resume) {
+    // Resuming an update so fetch the update manifest metadata first.
+    int64_t manifest_metadata_size = 0;
+    prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+    fetcher->AddRange(0, manifest_metadata_size);
+    // If there're remaining unprocessed data blobs, fetch them. Be careful not
+    // to request data beyond the end of the payload to avoid 416 HTTP response
+    // error codes.
+    int64_t next_data_offset = 0;
+    prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
+    uint64_t resume_offset = manifest_metadata_size + next_data_offset;
+    if (resume_offset < response_handler_action_->install_plan().payload_size) {
+      fetcher->AddRange(resume_offset);
+    }
+  } else {
+    fetcher->AddRange(0);
+  }
+}
+
+void UpdateAttempter::PingOmaha() {
+  if (!processor_->IsRunning()) {
+    shared_ptr<OmahaRequestAction> ping_action(
+        new OmahaRequestAction(system_state_,
+                               nullptr,
+                               new LibcurlHttpFetcher(GetProxyResolver(),
+                                                      system_state_),
+                               true));
+    actions_.push_back(shared_ptr<OmahaRequestAction>(ping_action));
+    processor_->set_delegate(nullptr);
+    processor_->EnqueueAction(ping_action.get());
+    // Call StartProcessing() synchronously here to avoid any race conditions
+    // caused by multiple outstanding ping Omaha requests.  If we call
+    // StartProcessing() asynchronously, the device can be suspended before we
+    // get a chance to callback to StartProcessing().  When the device resumes
+    // (assuming the device sleeps longer than the next update check period),
+    // StartProcessing() is called back and at the same time, the next update
+    // check is fired which eventually invokes StartProcessing().  A crash
+    // can occur because StartProcessing() checks to make sure that the
+    // processor is idle which it isn't due to the two concurrent ping Omaha
+    // requests.
+    processor_->StartProcessing();
+  } else {
+    LOG(WARNING) << "Action processor running, Omaha ping suppressed.";
+  }
+
+  // Update the last check time here; it may be re-updated when an Omaha
+  // response is received, but this will prevent us from repeatedly scheduling
+  // checks in the case where a response is not received.
+  UpdateLastCheckedTime();
+
+  // Update the status which will schedule the next update check
+  SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT);
+  ScheduleUpdates();
+}
+
+
+bool UpdateAttempter::DecrementUpdateCheckCount() {
+  int64_t update_check_count_value;
+
+  if (!prefs_->Exists(kPrefsUpdateCheckCount)) {
+    // This file does not exist. This means we haven't started our update
+    // check count down yet, so nothing more to do. This file will be created
+    // later when we first satisfy the wall-clock-based-wait period.
+    LOG(INFO) << "No existing update check count. That's normal.";
+    return true;
+  }
+
+  if (prefs_->GetInt64(kPrefsUpdateCheckCount, &update_check_count_value)) {
+    // Only if we're able to read a proper integer value, then go ahead
+    // and decrement and write back the result in the same file, if needed.
+    LOG(INFO) << "Update check count = " << update_check_count_value;
+
+    if (update_check_count_value == 0) {
+      // It could be 0, if, for some reason, the file didn't get deleted
+      // when we set our status to waiting for reboot. so we just leave it
+      // as is so that we can prevent another update_check wait for this client.
+      LOG(INFO) << "Not decrementing update check count as it's already 0.";
+      return true;
+    }
+
+    if (update_check_count_value > 0)
+      update_check_count_value--;
+    else
+      update_check_count_value = 0;
+
+    // Write out the new value of update_check_count_value.
+    if (prefs_->SetInt64(kPrefsUpdateCheckCount, update_check_count_value)) {
+      // We successfully wrote out te new value, so enable the
+      // update check based wait.
+      LOG(INFO) << "New update check count = " << update_check_count_value;
+      return true;
+    }
+  }
+
+  LOG(INFO) << "Deleting update check count state due to read/write errors.";
+
+  // We cannot read/write to the file, so disable the update check based wait
+  // so that we don't get stuck in this OS version by any chance (which could
+  // happen if there's some bug that causes to read/write incorrectly).
+  // Also attempt to delete the file to do our best effort to cleanup.
+  prefs_->Delete(kPrefsUpdateCheckCount);
+  return false;
+}
+
+
+void UpdateAttempter::UpdateEngineStarted() {
+  // If we just booted into a new update, keep the previous OS version
+  // in case we rebooted because of a crash of the old version, so we
+  // can do a proper crash report with correct information.
+  // This must be done before calling
+  // system_state_->payload_state()->UpdateEngineStarted() since it will
+  // delete SystemUpdated marker file.
+  if (system_state_->system_rebooted() &&
+      prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+    if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version_)) {
+      // If we fail to get the version string, make sure it stays empty.
+      prev_version_.clear();
+    }
+  }
+
+  system_state_->payload_state()->UpdateEngineStarted();
+  StartP2PAtStartup();
+}
+
+bool UpdateAttempter::StartP2PAtStartup() {
+  if (system_state_ == nullptr ||
+      !system_state_->p2p_manager()->IsP2PEnabled()) {
+    LOG(INFO) << "Not starting p2p at startup since it's not enabled.";
+    return false;
+  }
+
+  if (system_state_->p2p_manager()->CountSharedFiles() < 1) {
+    LOG(INFO) << "Not starting p2p at startup since our application "
+              << "is not sharing any files.";
+    return false;
+  }
+
+  return StartP2PAndPerformHousekeeping();
+}
+
+bool UpdateAttempter::StartP2PAndPerformHousekeeping() {
+  if (system_state_ == nullptr)
+    return false;
+
+  if (!system_state_->p2p_manager()->IsP2PEnabled()) {
+    LOG(INFO) << "Not starting p2p since it's not enabled.";
+    return false;
+  }
+
+  LOG(INFO) << "Ensuring that p2p is running.";
+  if (!system_state_->p2p_manager()->EnsureP2PRunning()) {
+    LOG(ERROR) << "Error starting p2p.";
+    return false;
+  }
+
+  LOG(INFO) << "Performing p2p housekeeping.";
+  if (!system_state_->p2p_manager()->PerformHousekeeping()) {
+    LOG(ERROR) << "Error performing housekeeping for p2p.";
+    return false;
+  }
+
+  LOG(INFO) << "Done performing p2p housekeeping.";
+  return true;
+}
+
+bool UpdateAttempter::GetBootTimeAtUpdate(Time *out_boot_time) {
+  if (update_completed_marker_.empty())
+    return false;
+
+  string contents;
+  if (!utils::ReadFile(update_completed_marker_, &contents))
+    return false;
+
+  char *endp;
+  int64_t stored_value = strtoll(contents.c_str(), &endp, 10);
+  if (*endp != '\0') {
+    LOG(ERROR) << "Error parsing file " << update_completed_marker_ << " "
+               << "with content '" << contents << "'";
+    return false;
+  }
+
+  *out_boot_time = Time::FromInternalValue(stored_value);
+  return true;
+}
+
+bool UpdateAttempter::IsUpdateRunningOrScheduled() {
+  return ((status_ != UPDATE_STATUS_IDLE &&
+           status_ != UPDATE_STATUS_UPDATED_NEED_REBOOT) ||
+          waiting_for_scheduled_check_);
+}
+
+bool UpdateAttempter::IsAnyUpdateSourceAllowed() {
+  // We allow updates from any source if either of these are true:
+  //  * The device is running an unofficial (dev/test) image.
+  //  * The debugd dev features are accessible (i.e. in devmode with no owner).
+  // This protects users running a base image, while still allowing a specific
+  // window (gated by the debug dev features) where `cros flash` is usable.
+  if (!system_state_->hardware()->IsOfficialBuild()) {
+    LOG(INFO) << "Non-official build; allowing any update source.";
+    return true;
+  }
+
+  // Even though the debugd tools are also gated on devmode, checking here can
+  // save us a D-Bus call so it's worth doing explicitly.
+  if (system_state_->hardware()->IsNormalBootMode()) {
+    LOG(INFO) << "Not in devmode; disallowing custom update sources.";
+    return false;
+  }
+
+  // Official images in devmode are allowed a custom update source iff the
+  // debugd dev tools are enabled.
+  GError* error = nullptr;
+  DBusGConnection* bus = dbus_iface_->BusGet(DBUS_BUS_SYSTEM, &error);
+  if (!bus) {
+    LOG(ERROR) << "Failed to get system bus: "
+               << utils::GetAndFreeGError(&error);
+    return false;
+  }
+
+  gint dev_features = debugd::DEV_FEATURES_DISABLED;
+  DBusGProxy* proxy = dbus_iface_->ProxyNewForName(
+      bus,
+      debugd::kDebugdServiceName,
+      debugd::kDebugdServicePath,
+      debugd::kDebugdInterface);
+  const gboolean success = dbus_iface_->ProxyCall_0_1(
+      proxy,
+      debugd::kQueryDevFeatures,
+      &error,
+      &dev_features);
+  dbus_iface_->ProxyUnref(proxy);
+
+  // Some boards may not include debugd so it's expected that this may fail,
+  // in which case we default to disallowing custom update sources.
+  if (success && !(dev_features & debugd::DEV_FEATURES_DISABLED)) {
+    LOG(INFO) << "Debugd dev tools enabled; allowing any update source.";
+    return true;
+  }
+  LOG(INFO) << "Debugd dev tools disabled; disallowing custom update sources.";
+  return false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
new file mode 100644
index 0000000..1326365
--- /dev/null
+++ b/update_attempter.h
@@ -0,0 +1,517 @@
+// Copyright (c) 2012 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_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
+
+#include <time.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/time/time.h>
+#include <glib.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/action_processor.h"
+#include "update_engine/chrome_browser_proxy_resolver.h"
+#include "update_engine/download_action.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/omaha_response_handler_action.h"
+#include "update_engine/proxy_resolver.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+
+class MetricsLibraryInterface;
+struct UpdateEngineService;
+
+namespace policy {
+class PolicyProvider;
+}
+
+namespace chromeos_update_engine {
+
+class DBusWrapperInterface;
+
+enum UpdateStatus {
+  UPDATE_STATUS_IDLE = 0,
+  UPDATE_STATUS_CHECKING_FOR_UPDATE,
+  UPDATE_STATUS_UPDATE_AVAILABLE,
+  UPDATE_STATUS_DOWNLOADING,
+  UPDATE_STATUS_VERIFYING,
+  UPDATE_STATUS_FINALIZING,
+  UPDATE_STATUS_UPDATED_NEED_REBOOT,
+  UPDATE_STATUS_REPORTING_ERROR_EVENT,
+  UPDATE_STATUS_ATTEMPTING_ROLLBACK,
+  UPDATE_STATUS_DISABLED,
+};
+
+const char* UpdateStatusToString(UpdateStatus status);
+
+class UpdateAttempter : public ActionProcessorDelegate,
+                        public DownloadActionDelegate {
+ public:
+  static const int kMaxDeltaUpdateFailures;
+
+  UpdateAttempter(SystemState* system_state,
+                  DBusWrapperInterface* dbus_iface);
+  ~UpdateAttempter() override;
+
+  // Further initialization to be done post construction.
+  void Init();
+
+  // Initiates scheduling of update checks.
+  virtual void ScheduleUpdates();
+
+  // Checks for update and, if a newer version is available, attempts to update
+  // the system. Non-empty |in_app_version| or |in_update_url| prevents
+  // automatic detection of the parameter.  |target_channel| denotes a
+  // policy-mandated channel we are updating to, if not empty. If |obey_proxies|
+  // is true, the update will likely respect Chrome's proxy setting. For
+  // security reasons, we may still not honor them. |interactive| should be true
+  // if this was called from the user (ie dbus).
+  virtual void Update(const std::string& app_version,
+                      const std::string& omaha_url,
+                      const std::string& target_channel,
+                      const std::string& target_version_prefix,
+                      bool obey_proxies,
+                      bool interactive);
+
+  // ActionProcessorDelegate methods:
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) override;
+  void ProcessingStopped(const ActionProcessor* processor) override;
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) override;
+
+  // Resets the current state to UPDATE_STATUS_IDLE.
+  // Used by update_engine_client for restarting a new update without
+  // having to reboot once the previous update has reached
+  // UPDATE_STATUS_UPDATED_NEED_REBOOT state. This is used only
+  // for testing purposes.
+  virtual bool ResetStatus();
+
+  // Returns the current status in the out params. Returns true on success.
+  virtual bool GetStatus(int64_t* last_checked_time,
+                         double* progress,
+                         std::string* current_operation,
+                         std::string* new_version,
+                         int64_t* new_size);
+
+  // Runs chromeos-setgoodkernel, whose responsibility it is to mark the
+  // currently booted partition has high priority/permanent/etc. The execution
+  // is asynchronous. On completion, the action processor may be started
+  // depending on the |start_action_processor_| field. Note that every update
+  // attempt goes through this method.
+  void UpdateBootFlags();
+
+  // Subprocess::Exec callback.
+  void CompleteUpdateBootFlags(int return_code);
+  static void StaticCompleteUpdateBootFlags(int return_code,
+                                            const std::string& output,
+                                            void* p);
+
+  UpdateStatus status() const { return status_; }
+
+  int http_response_code() const { return http_response_code_; }
+  void set_http_response_code(int code) { http_response_code_ = code; }
+
+  void set_dbus_service(struct UpdateEngineService* dbus_service) {
+    dbus_service_ = dbus_service;
+  }
+
+  // This is the internal entry point for going through an
+  // update. If the current status is idle invokes Update.
+  // This is called by the DBus implementation.
+  virtual void CheckForUpdate(const std::string& app_version,
+                              const std::string& omaha_url,
+                              bool is_interactive);
+
+  // This is the internal entry point for going through a rollback. This will
+  // attempt to run the postinstall on the non-active partition and set it as
+  // the partition to boot from. If |powerwash| is True, perform a powerwash
+  // as part of rollback. Returns True on success.
+  bool Rollback(bool powerwash);
+
+  // This is the internal entry point for checking if we can rollback.
+  bool CanRollback() const;
+
+  // This is the internal entry point for getting a rollback partition name,
+  // if one exists. It returns the bootable rollback kernel device partition
+  // name or empty string if none is available.
+  std::string GetRollbackPartition() const;
+
+  // Returns a list of available kernel partitions along with information
+  // whether it is possible to boot from it.
+  std::vector<std::pair<std::string, bool>> GetKernelDevices() const;
+
+  // Initiates a reboot if the current state is
+  // UPDATED_NEED_REBOOT. Returns true on sucess, false otherwise.
+  bool RebootIfNeeded();
+
+  // DownloadActionDelegate methods
+  void SetDownloadStatus(bool active) override;
+  void BytesReceived(uint64_t bytes_received, uint64_t total) override;
+
+  // Broadcasts the current status over D-Bus.
+  void BroadcastStatus();
+
+  // Returns the special flags to be added to ErrorCode values based on the
+  // parameters used in the current update attempt.
+  uint32_t GetErrorCodeFlags();
+
+  // Returns true if we should cancel the current download attempt based on the
+  // current state of the system, in which case |cancel_reason| indicates the
+  // reason for the cancellation.  False otherwise, in which case
+  // |cancel_reason| is untouched.
+  bool ShouldCancel(ErrorCode* cancel_reason);
+
+  // Called at update_engine startup to do various house-keeping.
+  void UpdateEngineStarted();
+
+  // Reloads the device policy from libchromeos. Note: This method doesn't
+  // cause a real-time policy fetch from the policy server. It just reloads the
+  // latest value that libchromeos has cached. libchromeos fetches the policies
+  // from the server asynchronously at its own frequency.
+  virtual void RefreshDevicePolicy();
+
+  // Returns the boottime (CLOCK_BOOTTIME) recorded at the last
+  // successful update. Returns false if the device has not updated.
+  virtual bool GetBootTimeAtUpdate(base::Time *out_boot_time);
+
+  // Returns a version OS version that was being used before the last reboot,
+  // and if that reboot happended to be into an update (current version).
+  // This will return an empty string otherwise.
+  std::string const& GetPrevVersion() const { return prev_version_; }
+
+  // Returns the number of consecutive failed update checks.
+  virtual unsigned int consecutive_failed_update_checks() const {
+    return consecutive_failed_update_checks_;
+  }
+
+  // Returns the poll interval dictated by Omaha, if provided; zero otherwise.
+  virtual unsigned int server_dictated_poll_interval() const {
+    return server_dictated_poll_interval_;
+  }
+
+  // Sets a callback to be used when either a forced update request is received
+  // (first argument set to true) or cleared by an update attempt (first
+  // argument set to false). The callback further encodes whether the forced
+  // check is an interactive one (second argument set to true). Takes ownership
+  // of the callback object. A null value disables callback on these events.
+  // Note that only one callback can be set, so effectively at most one client
+  // can be notified.
+  virtual void set_forced_update_pending_callback(
+      base::Callback<void(bool, bool)>*  // NOLINT(readability/function)
+      callback) {
+    forced_update_pending_callback_.reset(callback);
+  }
+
+  // Returns true if we should allow updates from any source. In official builds
+  // we want to restrict updates to known safe sources, but under certain
+  // conditions it's useful to allow updating from anywhere (e.g. to allow
+  // 'cros flash' to function properly).
+  virtual bool IsAnyUpdateSourceAllowed();
+
+ private:
+  // Update server URL for automated lab test.
+  static const char* const kTestUpdateUrl;
+
+  // Special ctor + friend declarations for testing purposes.
+  UpdateAttempter(SystemState* system_state,
+                  DBusWrapperInterface* dbus_iface,
+                  const std::string& update_completed_marker);
+
+  friend class UpdateAttempterUnderTest;
+  friend class UpdateAttempterTest;
+  FRIEND_TEST(UpdateAttempterTest, ActionCompletedDownloadTest);
+  FRIEND_TEST(UpdateAttempterTest, ActionCompletedErrorTest);
+  FRIEND_TEST(UpdateAttempterTest, ActionCompletedOmahaRequestTest);
+  FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventTest);
+  FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+  FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
+  FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
+  FRIEND_TEST(UpdateAttempterTest, ReadTrackFromPolicy);
+  FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
+  FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
+  FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+  FRIEND_TEST(UpdateAttempterTest, UpdateTest);
+  FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+  FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
+
+  // Checks if it's more than 24 hours since daily metrics were last
+  // reported and, if so, reports daily metrics. Returns |true| if
+  // metrics were reported, |false| otherwise.
+  bool CheckAndReportDailyMetrics();
+
+  // Calculates and reports the age of the currently running OS. This
+  // is defined as the age of the /etc/lsb-release file.
+  void ReportOSAge();
+
+  // Sets the status to the given status and notifies a status update over dbus.
+  void SetStatusAndNotify(UpdateStatus status);
+
+  // Sets up the download parameters after receiving the update check response.
+  void SetupDownload();
+
+  // Creates an error event object in |error_event_| to be included in an
+  // OmahaRequestAction once the current action processor is done.
+  void CreatePendingErrorEvent(AbstractAction* action, ErrorCode code);
+
+  // If there's a pending error event allocated in |error_event_|, schedules an
+  // OmahaRequestAction with that event in the current processor, clears the
+  // pending event, updates the status and returns true. Returns false
+  // otherwise.
+  bool ScheduleErrorEventAction();
+
+  // Sets the cpu shares to |shares| and updates |shares_| if the new
+  // |shares| is different than the current |shares_|, otherwise simply
+  // returns.
+  void SetCpuShares(utils::CpuShares shares);
+
+  // Sets the cpu shares to low and sets up timeout events to increase it.
+  void SetupCpuSharesManagement();
+
+  // Resets the cpu shares to normal and destroys any scheduled timeout
+  // sources.
+  void CleanupCpuSharesManagement();
+
+  // The cpu shares timeout source callback sets the current cpu shares to
+  // normal.
+  void ManageCpuSharesCallback();
+
+  // Schedules an event loop callback to start the action processor. This is
+  // scheduled asynchronously to unblock the event loop.
+  void ScheduleProcessingStart();
+
+  // Checks if a full update is needed and forces it by updating the Omaha
+  // request params.
+  void DisableDeltaUpdateIfNeeded();
+
+  // If this was a delta update attempt that failed, count it so that a full
+  // update can be tried when needed.
+  void MarkDeltaUpdateFailure();
+
+  ProxyResolver* GetProxyResolver() {
+    return obeying_proxies_ ?
+        reinterpret_cast<ProxyResolver*>(&chrome_proxy_resolver_) :
+        reinterpret_cast<ProxyResolver*>(&direct_proxy_resolver_);
+  }
+
+  // Sends a ping to Omaha.
+  // This is used after an update has been applied and we're waiting for the
+  // user to reboot.  This ping helps keep the number of actives count
+  // accurate in case a user takes a long time to reboot the device after an
+  // update has been applied.
+  void PingOmaha();
+
+  // Helper method of Update() to calculate the update-related parameters
+  // from various sources and set the appropriate state. Please refer to
+  // Update() method for the meaning of the parametes.
+  bool CalculateUpdateParams(const std::string& app_version,
+                             const std::string& omaha_url,
+                             const std::string& target_channel,
+                             const std::string& target_version_prefix,
+                             bool obey_proxies,
+                             bool interactive);
+
+  // Calculates all the scattering related parameters (such as waiting period,
+  // which type of scattering is enabled, etc.) and also updates/deletes
+  // the corresponding prefs file used in scattering. Should be called
+  // only after the device policy has been loaded and set in the system_state_.
+  void CalculateScatteringParams(bool is_interactive);
+
+  // Sets a random value for the waiting period to wait for before downloading
+  // an update, if one available. This value will be upperbounded by the
+  // scatter factor value specified from policy.
+  void GenerateNewWaitingPeriod();
+
+  // Helper method of Update() and Rollback() to construct the sequence of
+  // actions to be performed for the postinstall.
+  // |previous_action| is the previous action to get
+  // bonded with the install_plan that gets passed to postinstall.
+  void BuildPostInstallActions(InstallPlanAction* previous_action);
+
+  // Helper method of Update() to construct the sequence of actions to
+  // be performed for an update check. Please refer to
+  // Update() method for the meaning of the parameters.
+  void BuildUpdateActions(bool interactive);
+
+  // Decrements the count in the kUpdateCheckCountFilePath.
+  // Returns True if successfully decremented, false otherwise.
+  bool DecrementUpdateCheckCount();
+
+  // Starts p2p and performs housekeeping. Returns true only if p2p is
+  // running and housekeeping was done.
+  bool StartP2PAndPerformHousekeeping();
+
+  // Calculates whether peer-to-peer should be used. Sets the
+  // |use_p2p_to_download_| and |use_p2p_to_share_| parameters
+  // on the |omaha_request_params_| object.
+  void CalculateP2PParams(bool interactive);
+
+  // Starts P2P if it's enabled and there are files to actually share.
+  // Called only at program startup. Returns true only if p2p was
+  // started and housekeeping was performed.
+  bool StartP2PAtStartup();
+
+  // Writes to the processing completed marker. Does nothing if
+  // |update_completed_marker_| is empty.
+  void WriteUpdateCompletedMarker();
+
+  // Sends a D-Bus message to the Chrome OS power manager asking it to reboot
+  // the system. Returns true on success.
+  bool RequestPowerManagerReboot();
+
+  // Reboots the system directly by calling /sbin/shutdown. Returns true on
+  // success.
+  bool RebootDirectly();
+
+  // Callback for the async UpdateCheckAllowed policy request. If |status| is
+  // |EvalStatus::kSucceeded|, either runs or suppresses periodic update checks,
+  // based on the content of |params|. Otherwise, retries the policy request.
+  void OnUpdateScheduled(
+      chromeos_update_manager::EvalStatus status,
+      const chromeos_update_manager::UpdateCheckParams& params);
+
+  // Updates the time an update was last attempted to the current time.
+  void UpdateLastCheckedTime();
+
+  // Returns whether an update is currently running or scheduled.
+  bool IsUpdateRunningOrScheduled();
+
+  // Last status notification timestamp used for throttling. Use monotonic
+  // TimeTicks to ensure that notifications are sent even if the system clock is
+  // set back in the middle of an update.
+  base::TimeTicks last_notify_time_;
+
+  std::vector<std::shared_ptr<AbstractAction>> actions_;
+  std::unique_ptr<ActionProcessor> processor_;
+
+  // External state of the system outside the update_engine process
+  // carved out separately to mock out easily in unit tests.
+  SystemState* system_state_;
+
+  // Interface for getting D-Bus connections.
+  DBusWrapperInterface* dbus_iface_ = nullptr;
+
+  // If non-null, this UpdateAttempter will send status updates over this
+  // dbus service.
+  UpdateEngineService* dbus_service_ = nullptr;
+
+  // Pointer to the OmahaResponseHandlerAction in the actions_ vector.
+  std::shared_ptr<OmahaResponseHandlerAction> response_handler_action_;
+
+  // Pointer to the DownloadAction in the actions_ vector.
+  std::shared_ptr<DownloadAction> download_action_;
+
+  // Pointer to the preferences store interface. This is just a cached
+  // copy of system_state->prefs() because it's used in many methods and
+  // is convenient this way.
+  PrefsInterface* prefs_ = nullptr;
+
+  // Pending error event, if any.
+  std::unique_ptr<OmahaEvent> error_event_;
+
+  // If we should request a reboot even tho we failed the update
+  bool fake_update_success_ = false;
+
+  // HTTP server response code from the last HTTP request action.
+  int http_response_code_ = 0;
+
+  // Current cpu shares.
+  utils::CpuShares shares_ = utils::kCpuSharesNormal;
+
+  // The cpu shares management timeout task id.
+  chromeos::MessageLoop::TaskId manage_shares_id_{
+      chromeos::MessageLoop::kTaskIdNull};
+
+  // Set to true if an update download is active (and BytesReceived
+  // will be called), set to false otherwise.
+  bool download_active_ = false;
+
+  // For status:
+  UpdateStatus status_;
+  double download_progress_ = 0.0;
+  int64_t last_checked_time_ = 0;
+  std::string prev_version_;
+  std::string new_version_ = "0.0.0.0";
+  int64_t new_payload_size_ = 0;
+
+  // Common parameters for all Omaha requests.
+  OmahaRequestParams* omaha_request_params_ = nullptr;
+
+  // Number of consecutive manual update checks we've had where we obeyed
+  // Chrome's proxy settings.
+  int proxy_manual_checks_ = 0;
+
+  // If true, this update cycle we are obeying proxies
+  bool obeying_proxies_ = true;
+
+  // Our two proxy resolvers
+  DirectProxyResolver direct_proxy_resolver_;
+  ChromeBrowserProxyResolver chrome_proxy_resolver_;
+
+  // Originally, both of these flags are false. Once UpdateBootFlags is called,
+  // |update_boot_flags_running_| is set to true. As soon as UpdateBootFlags
+  // completes its asynchronous run, |update_boot_flags_running_| is reset to
+  // false and |updated_boot_flags_| is set to true. From that point on there
+  // will be no more changes to these flags.
+  //
+  // True if UpdateBootFlags has completed.
+  bool updated_boot_flags_ = false;
+  // True if UpdateBootFlags is running.
+  bool update_boot_flags_running_ = false;
+
+  // The command to run to set the current kernel as good.
+  std::string set_good_kernel_cmd_ = "/usr/sbin/chromeos-setgoodkernel";
+
+  // True if the action processor needs to be started by the boot flag updater.
+  bool start_action_processor_ = false;
+
+  // Used for fetching information about the device policy.
+  std::unique_ptr<policy::PolicyProvider> policy_provider_;
+
+  // The current scatter factor as found in the policy setting.
+  base::TimeDelta scatter_factor_;
+
+  // Update completed marker file. An empty string means this marker is being
+  // ignored (nor is it being written), which is useful for testing situations.
+  std::string update_completed_marker_;
+
+  // The number of consecutive failed update checks. Needed for calculating the
+  // next update check interval.
+  unsigned int consecutive_failed_update_checks_ = 0;
+
+  // The poll interval (in seconds) that was dictated by Omaha, if any; zero
+  // otherwise. This is needed for calculating the update check interval.
+  unsigned int server_dictated_poll_interval_ = 0;
+
+  // Tracks whether we have scheduled update checks.
+  bool waiting_for_scheduled_check_ = false;
+
+  // A callback to use when a forced update request is either received (true) or
+  // cleared by an update attempt (false). The second argument indicates whether
+  // this is an interactive update, and its value is significant iff the first
+  // argument is true.
+  std::unique_ptr<base::Callback<void(bool, bool)>>
+      forced_update_pending_callback_;
+
+  // The |app_version| and |omaha_url| parameters received during the latest
+  // forced update request. They are retrieved for use once the update is
+  // actually scheduled.
+  std::string forced_app_version_;
+  std::string forced_omaha_url_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
new file mode 100644
index 0000000..41a8232
--- /dev/null
+++ b/update_attempter_unittest.cc
@@ -0,0 +1,1022 @@
+// Copyright (c) 2012 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/update_attempter.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/glib_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/filesystem_verifier_action.h"
+#include "update_engine/install_plan.h"
+#include "update_engine/mock_action.h"
+#include "update_engine/mock_action_processor.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/mock_p2p_manager.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/postinstall_runner_action.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using testing::A;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Ne;
+using testing::NiceMock;
+using testing::Property;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+// Test a subclass rather than the main class directly so that we can mock out
+// methods within the class. There're explicit unit tests for the mocked out
+// methods.
+class UpdateAttempterUnderTest : public UpdateAttempter {
+ public:
+  // We always feed an explicit update completed marker name; however, unless
+  // explicitly specified, we feed an empty string, which causes the
+  // UpdateAttempter class to ignore / not write the marker file.
+  UpdateAttempterUnderTest(SystemState* system_state,
+                           DBusWrapperInterface* dbus_iface)
+      : UpdateAttempterUnderTest(system_state, dbus_iface, "") {}
+
+  UpdateAttempterUnderTest(SystemState* system_state,
+                           DBusWrapperInterface* dbus_iface,
+                           const string& update_completed_marker)
+      : UpdateAttempter(system_state, dbus_iface, update_completed_marker) {}
+
+  // Wrap the update scheduling method, allowing us to opt out of scheduled
+  // updates for testing purposes.
+  void ScheduleUpdates() override {
+    schedule_updates_called_ = true;
+    if (do_schedule_updates_) {
+      UpdateAttempter::ScheduleUpdates();
+    } else {
+      LOG(INFO) << "[TEST] Update scheduling disabled.";
+    }
+  }
+  void EnableScheduleUpdates() { do_schedule_updates_ = true; }
+  void DisableScheduleUpdates() { do_schedule_updates_ = false; }
+
+  // Indicates whether ScheduleUpdates() was called.
+  bool schedule_updates_called() const { return schedule_updates_called_; }
+
+  // Need to expose forced_omaha_url_ so we can test it.
+  const string& forced_omaha_url() const { return forced_omaha_url_; }
+
+ private:
+  bool schedule_updates_called_ = false;
+  bool do_schedule_updates_ = true;
+};
+
+class UpdateAttempterTest : public ::testing::Test {
+ protected:
+  UpdateAttempterTest()
+      : attempter_(&fake_system_state_, &dbus_),
+        mock_connection_manager(&fake_system_state_),
+        fake_dbus_system_bus_(reinterpret_cast<DBusGConnection*>(1)),
+        fake_dbus_debugd_proxy_(reinterpret_cast<DBusGProxy*>(2)) {
+    // Override system state members.
+    fake_system_state_.set_connection_manager(&mock_connection_manager);
+    fake_system_state_.set_update_attempter(&attempter_);
+    loop_.SetAsCurrent();
+
+    // Finish initializing the attempter.
+    attempter_.Init();
+
+    // We set the set_good_kernel command to a non-existent path so it fails to
+    // run it. This avoids the async call to the command and continues the
+    // update process right away. Tests testing that behavior can override the
+    // default set_good_kernel command if needed.
+    attempter_.set_good_kernel_cmd_ = "/path/to/non-existent/command";
+  }
+
+  void SetUp() override {
+    CHECK(utils::MakeTempDirectory("UpdateAttempterTest-XXXXXX", &test_dir_));
+
+    EXPECT_EQ(nullptr, attempter_.dbus_service_);
+    EXPECT_NE(nullptr, attempter_.system_state_);
+    EXPECT_EQ(0, attempter_.http_response_code_);
+    EXPECT_EQ(utils::kCpuSharesNormal, attempter_.shares_);
+    EXPECT_EQ(MessageLoop::kTaskIdNull, attempter_.manage_shares_id_);
+    EXPECT_FALSE(attempter_.download_active_);
+    EXPECT_EQ(UPDATE_STATUS_IDLE, attempter_.status_);
+    EXPECT_EQ(0.0, attempter_.download_progress_);
+    EXPECT_EQ(0, attempter_.last_checked_time_);
+    EXPECT_EQ("0.0.0.0", attempter_.new_version_);
+    EXPECT_EQ(0, attempter_.new_payload_size_);
+    processor_ = new NiceMock<MockActionProcessor>();
+    attempter_.processor_.reset(processor_);  // Transfers ownership.
+    prefs_ = fake_system_state_.mock_prefs();
+
+    // Set up store/load semantics of P2P properties via the mock PayloadState.
+    actual_using_p2p_for_downloading_ = false;
+    EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+                SetUsingP2PForDownloading(_))
+        .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_downloading_));
+    EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+                GetUsingP2PForDownloading())
+        .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_downloading_));
+    actual_using_p2p_for_sharing_ = false;
+    EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+                SetUsingP2PForSharing(_))
+        .WillRepeatedly(SaveArg<0>(&actual_using_p2p_for_sharing_));
+    EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+                GetUsingP2PForDownloading())
+        .WillRepeatedly(ReturnPointee(&actual_using_p2p_for_sharing_));
+
+    // Set up mock debugd access over the system D-Bus. ProxyCall_0_1() also
+    // needs to be mocked in any test using debugd to provide the desired value.
+    ON_CALL(dbus_, BusGet(DBUS_BUS_SYSTEM, _))
+        .WillByDefault(Return(fake_dbus_system_bus_));
+    ON_CALL(dbus_, ProxyNewForName(fake_dbus_system_bus_,
+                                   StrEq(debugd::kDebugdServiceName),
+                                   StrEq(debugd::kDebugdServicePath),
+                                   StrEq(debugd::kDebugdInterface)))
+        .WillByDefault(Return(fake_dbus_debugd_proxy_));
+  }
+
+  void TearDown() override {
+    test_utils::RecursiveUnlinkDir(test_dir_);
+    EXPECT_EQ(0, MessageLoopRunMaxIterations(&loop_, 1));
+  }
+
+ public:
+  void ScheduleQuitMainLoop();
+
+  // Callbacks to run the different tests from the main loop.
+  void UpdateTestStart();
+  void UpdateTestVerify();
+  void RollbackTestStart(bool enterprise_rollback, bool valid_slot);
+  void RollbackTestVerify();
+  void PingOmahaTestStart();
+  void ReadScatterFactorFromPolicyTestStart();
+  void DecrementUpdateCheckCountTestStart();
+  void NoScatteringDoneDuringManualUpdateTestStart();
+  void P2PNotEnabledStart();
+  void P2PEnabledStart();
+  void P2PEnabledInteractiveStart();
+  void P2PEnabledStartingFailsStart();
+  void P2PEnabledHousekeepingFailsStart();
+
+  bool actual_using_p2p_for_downloading() {
+    return actual_using_p2p_for_downloading_;
+  }
+  bool actual_using_p2p_for_sharing() {
+    return actual_using_p2p_for_sharing_;
+  }
+
+  // TODO(deymo): Replace this with a FakeMessageLoop. Some of these tests use a
+  // real LibcurlHttpFetcher, which still requires a GlibMessageLoop.
+  chromeos::GlibMessageLoop loop_;
+
+  FakeSystemState fake_system_state_;
+  NiceMock<MockDBusWrapper> dbus_;
+  UpdateAttempterUnderTest attempter_;
+  NiceMock<MockActionProcessor>* processor_;
+  NiceMock<MockPrefs>* prefs_;  // Shortcut to fake_system_state_->mock_prefs().
+  NiceMock<MockConnectionManager> mock_connection_manager;
+  // fake_dbus_xxx pointers will be non-null for comparison purposes, but won't
+  // be valid objects so don't try to use them.
+  DBusGConnection* fake_dbus_system_bus_;
+  DBusGProxy* fake_dbus_debugd_proxy_;
+
+  string test_dir_;
+
+  bool actual_using_p2p_for_downloading_;
+  bool actual_using_p2p_for_sharing_;
+};
+
+void UpdateAttempterTest::ScheduleQuitMainLoop() {
+  loop_.PostTask(FROM_HERE, base::Bind([this] { this->loop_.BreakLoop(); }));
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedDownloadTest) {
+  unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
+  fetcher->FailTransfer(503);  // Sets the HTTP response code.
+  DownloadAction action(prefs_, nullptr, fetcher.release());
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+  attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+  EXPECT_EQ(503, attempter_.http_response_code());
+  EXPECT_EQ(UPDATE_STATUS_FINALIZING, attempter_.status());
+  ASSERT_EQ(nullptr, attempter_.error_event_.get());
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedErrorTest) {
+  MockAction action;
+  EXPECT_CALL(action, Type()).WillRepeatedly(Return("MockAction"));
+  attempter_.status_ = UPDATE_STATUS_DOWNLOADING;
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(Return(false));
+  attempter_.ActionCompleted(nullptr, &action, ErrorCode::kError);
+  ASSERT_NE(nullptr, attempter_.error_event_.get());
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedOmahaRequestTest) {
+  unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
+  fetcher->FailTransfer(500);  // Sets the HTTP response code.
+  OmahaRequestAction action(&fake_system_state_, nullptr,
+                            fetcher.release(), false);
+  ObjectCollectorAction<OmahaResponse> collector_action;
+  BondActions(&action, &collector_action);
+  OmahaResponse response;
+  response.poll_interval = 234;
+  action.SetOutputObject(response);
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+  attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+  EXPECT_EQ(500, attempter_.http_response_code());
+  EXPECT_EQ(UPDATE_STATUS_IDLE, attempter_.status());
+  EXPECT_EQ(234, attempter_.server_dictated_poll_interval_);
+  ASSERT_TRUE(attempter_.error_event_.get() == nullptr);
+}
+
+TEST_F(UpdateAttempterTest, RunAsRootConstructWithUpdatedMarkerTest) {
+  string test_update_completed_marker;
+  CHECK(utils::MakeTempFile(
+          "update_attempter_unittest-update_completed_marker-XXXXXX",
+          &test_update_completed_marker, nullptr));
+  ScopedPathUnlinker completed_marker_unlinker(test_update_completed_marker);
+  const base::FilePath marker(test_update_completed_marker);
+  EXPECT_EQ(0, base::WriteFile(marker, "", 0));
+  UpdateAttempterUnderTest attempter(&fake_system_state_, &dbus_,
+                                     test_update_completed_marker);
+  EXPECT_EQ(UPDATE_STATUS_UPDATED_NEED_REBOOT, attempter.status());
+}
+
+TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) {
+  extern ErrorCode GetErrorCodeForAction(AbstractAction* action,
+                                              ErrorCode code);
+  EXPECT_EQ(ErrorCode::kSuccess,
+            GetErrorCodeForAction(nullptr, ErrorCode::kSuccess));
+
+  FakeSystemState fake_system_state;
+  OmahaRequestAction omaha_request_action(&fake_system_state, nullptr,
+                                          nullptr, false);
+  EXPECT_EQ(ErrorCode::kOmahaRequestError,
+            GetErrorCodeForAction(&omaha_request_action, ErrorCode::kError));
+  OmahaResponseHandlerAction omaha_response_handler_action(&fake_system_state_);
+  EXPECT_EQ(ErrorCode::kOmahaResponseHandlerError,
+            GetErrorCodeForAction(&omaha_response_handler_action,
+                                  ErrorCode::kError));
+  FilesystemVerifierAction filesystem_verifier_action(
+      &fake_system_state_, PartitionType::kRootfs);
+  EXPECT_EQ(ErrorCode::kFilesystemVerifierError,
+            GetErrorCodeForAction(&filesystem_verifier_action,
+                                  ErrorCode::kError));
+  PostinstallRunnerAction postinstall_runner_action;
+  EXPECT_EQ(ErrorCode::kPostinstallRunnerError,
+            GetErrorCodeForAction(&postinstall_runner_action,
+                                  ErrorCode::kError));
+  MockAction action_mock;
+  EXPECT_CALL(action_mock, Type()).WillOnce(Return("MockAction"));
+  EXPECT_EQ(ErrorCode::kError,
+            GetErrorCodeForAction(&action_mock, ErrorCode::kError));
+}
+
+TEST_F(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest) {
+  attempter_.omaha_request_params_->set_delta_okay(true);
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(Return(false));
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures - 1),
+          Return(true)));
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+          Return(true)));
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay());
+  EXPECT_CALL(*prefs_, GetInt64(_, _)).Times(0);
+  attempter_.DisableDeltaUpdateIfNeeded();
+  EXPECT_FALSE(attempter_.omaha_request_params_->delta_okay());
+}
+
+TEST_F(UpdateAttempterTest, MarkDeltaUpdateFailureTest) {
+  EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+      .WillOnce(Return(false))
+      .WillOnce(DoAll(SetArgumentPointee<1>(-1), Return(true)))
+      .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(true)))
+      .WillOnce(DoAll(
+          SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures),
+          Return(true)));
+  EXPECT_CALL(*prefs_, SetInt64(Ne(kPrefsDeltaUpdateFailures), _))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 1)).Times(2);
+  EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures, 2));
+  EXPECT_CALL(*prefs_, SetInt64(kPrefsDeltaUpdateFailures,
+                               UpdateAttempter::kMaxDeltaUpdateFailures + 1));
+  for (int i = 0; i < 4; i ++)
+    attempter_.MarkDeltaUpdateFailure();
+}
+
+TEST_F(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest) {
+  EXPECT_CALL(*processor_, EnqueueAction(_)).Times(0);
+  EXPECT_CALL(*processor_, StartProcessing()).Times(0);
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(_))
+      .Times(0);
+  OmahaResponse response;
+  string url1 = "http://url1";
+  response.payload_urls.push_back(url1);
+  response.payload_urls.push_back("https://url");
+  EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
+      .WillRepeatedly(Return(url1));
+  fake_system_state_.mock_payload_state()->SetResponse(response);
+  attempter_.ScheduleErrorEventAction();
+  EXPECT_EQ(url1, fake_system_state_.mock_payload_state()->GetCurrentUrl());
+}
+
+TEST_F(UpdateAttempterTest, ScheduleErrorEventActionTest) {
+  EXPECT_CALL(*processor_,
+              EnqueueAction(Property(&AbstractAction::Type,
+                                     OmahaRequestAction::StaticType())));
+  EXPECT_CALL(*processor_, StartProcessing());
+  ErrorCode err = ErrorCode::kError;
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(err));
+  attempter_.error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete,
+                                               OmahaEvent::kResultError,
+                                               err));
+  attempter_.ScheduleErrorEventAction();
+  EXPECT_EQ(UPDATE_STATUS_REPORTING_ERROR_EVENT, attempter_.status());
+}
+
+namespace {
+// Actions that will be built as part of an update check.
+const string kUpdateActionTypes[] = {  // NOLINT(runtime/string)
+  OmahaRequestAction::StaticType(),
+  OmahaResponseHandlerAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
+  OmahaRequestAction::StaticType(),
+  DownloadAction::StaticType(),
+  OmahaRequestAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
+  FilesystemVerifierAction::StaticType(),
+  PostinstallRunnerAction::StaticType(),
+  OmahaRequestAction::StaticType()
+};
+
+// Actions that will be built as part of a user-initiated rollback.
+const string kRollbackActionTypes[] = {  // NOLINT(runtime/string)
+  InstallPlanAction::StaticType(),
+  PostinstallRunnerAction::StaticType(),
+};
+
+}  // namespace
+
+void UpdateAttempterTest::UpdateTestStart() {
+  attempter_.set_http_response_code(200);
+
+  // Expect that the device policy is loaded by the UpdateAttempter at some
+  // point by calling RefreshDevicePolicy.
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+  EXPECT_CALL(*device_policy, LoadPolicy())
+      .Times(testing::AtLeast(1)).WillRepeatedly(Return(true));
+
+  {
+    InSequence s;
+    for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+      EXPECT_CALL(*processor_,
+                  EnqueueAction(Property(&AbstractAction::Type,
+                                         kUpdateActionTypes[i])));
+    }
+    EXPECT_CALL(*processor_, StartProcessing());
+  }
+
+  attempter_.Update("", "", "", "", false, false);
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::UpdateTestVerify,
+                            base::Unretained(this)));
+}
+
+void UpdateAttempterTest::UpdateTestVerify() {
+  EXPECT_EQ(0, attempter_.http_response_code());
+  EXPECT_EQ(&attempter_, processor_->delegate());
+  EXPECT_EQ(arraysize(kUpdateActionTypes), attempter_.actions_.size());
+  for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+    EXPECT_EQ(kUpdateActionTypes[i], attempter_.actions_[i]->Type());
+  }
+  EXPECT_EQ(attempter_.response_handler_action_.get(),
+            attempter_.actions_[1].get());
+  DownloadAction* download_action =
+      dynamic_cast<DownloadAction*>(attempter_.actions_[5].get());
+  ASSERT_NE(nullptr, download_action);
+  EXPECT_EQ(&attempter_, download_action->delegate());
+  EXPECT_EQ(UPDATE_STATUS_CHECKING_FOR_UPDATE, attempter_.status());
+  loop_.BreakLoop();
+}
+
+void UpdateAttempterTest::RollbackTestStart(
+    bool enterprise_rollback, bool valid_slot) {
+  // Create a device policy so that we can change settings.
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy);
+
+  if (!valid_slot) {
+    // References bootable kernels in fake_hardware.h
+    string rollback_kernel = "/dev/sdz2";
+    LOG(INFO) << "Test Mark Unbootable: " << rollback_kernel;
+    fake_system_state_.fake_hardware()->MarkKernelUnbootable(
+        rollback_kernel);
+  }
+
+  bool is_rollback_allowed = false;
+
+  // We only allow rollback on devices that are not enterprise enrolled and
+  // which have a valid slot to rollback to.
+  if (!enterprise_rollback && valid_slot) {
+     is_rollback_allowed = true;
+  }
+
+  if (enterprise_rollback) {
+    // We return an empty owner as this is an enterprise.
+    EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+        DoAll(SetArgumentPointee<0>(string("")),
+        Return(true)));
+  } else {
+    // We return a fake owner as this is an owned consumer device.
+    EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+        DoAll(SetArgumentPointee<0>(string("fake.mail@fake.com")),
+        Return(true)));
+  }
+
+  if (is_rollback_allowed) {
+    InSequence s;
+    for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+      EXPECT_CALL(*processor_,
+                  EnqueueAction(Property(&AbstractAction::Type,
+                                         kRollbackActionTypes[i])));
+    }
+    EXPECT_CALL(*processor_, StartProcessing());
+
+    EXPECT_TRUE(attempter_.Rollback(true));
+    loop_.PostTask(FROM_HERE,
+                   base::Bind(&UpdateAttempterTest::RollbackTestVerify,
+                              base::Unretained(this)));
+  } else {
+    EXPECT_FALSE(attempter_.Rollback(true));
+    loop_.BreakLoop();
+  }
+}
+
+void UpdateAttempterTest::RollbackTestVerify() {
+  // Verifies the actions that were enqueued.
+  EXPECT_EQ(&attempter_, processor_->delegate());
+  EXPECT_EQ(arraysize(kRollbackActionTypes), attempter_.actions_.size());
+  for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+    EXPECT_EQ(kRollbackActionTypes[i], attempter_.actions_[i]->Type());
+  }
+  EXPECT_EQ(UPDATE_STATUS_ATTEMPTING_ROLLBACK, attempter_.status());
+  InstallPlanAction* install_plan_action =
+        dynamic_cast<InstallPlanAction*>(attempter_.actions_[0].get());
+  InstallPlan* install_plan = install_plan_action->install_plan();
+  // Matches fake_hardware.h -> rollback should move from kernel/boot device
+  // pair to other pair.
+  EXPECT_EQ(install_plan->install_path, string("/dev/sdz3"));
+  EXPECT_EQ(install_plan->kernel_install_path, string("/dev/sdz2"));
+  EXPECT_EQ(install_plan->powerwash_required, true);
+  loop_.BreakLoop();
+}
+
+TEST_F(UpdateAttempterTest, UpdateTest) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::UpdateTestStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, RollbackTest) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::RollbackTestStart,
+                            base::Unretained(this),
+                            false, true));
+  loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, InvalidSlotRollbackTest) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::RollbackTestStart,
+                            base::Unretained(this),
+                            false, false));
+  loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, EnterpriseRollbackTest) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::RollbackTestStart,
+                            base::Unretained(this),
+                            true, true));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::PingOmahaTestStart() {
+  EXPECT_CALL(*processor_,
+              EnqueueAction(Property(&AbstractAction::Type,
+                                     OmahaRequestAction::StaticType())));
+  EXPECT_CALL(*processor_, StartProcessing());
+  attempter_.PingOmaha();
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, PingOmahaTest) {
+  EXPECT_FALSE(attempter_.waiting_for_scheduled_check_);
+  EXPECT_FALSE(attempter_.schedule_updates_called());
+  // Disable scheduling of subsequnet checks; we're using the DefaultPolicy in
+  // testing, which is more permissive than we want to handle here.
+  attempter_.DisableScheduleUpdates();
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::PingOmahaTestStart,
+                            base::Unretained(this)));
+  chromeos::MessageLoopRunMaxIterations(&loop_, 100);
+  EXPECT_EQ(UPDATE_STATUS_UPDATED_NEED_REBOOT, attempter_.status());
+  EXPECT_TRUE(attempter_.schedule_updates_called());
+}
+
+TEST_F(UpdateAttempterTest, CreatePendingErrorEventTest) {
+  MockAction action;
+  const ErrorCode kCode = ErrorCode::kDownloadTransferError;
+  attempter_.CreatePendingErrorEvent(&action, kCode);
+  ASSERT_NE(nullptr, attempter_.error_event_.get());
+  EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+  EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result);
+  EXPECT_EQ(
+      static_cast<ErrorCode>(static_cast<int>(kCode) |
+                             static_cast<int>(ErrorCode::kTestOmahaUrlFlag)),
+      attempter_.error_event_->error_code);
+}
+
+TEST_F(UpdateAttempterTest, CreatePendingErrorEventResumedTest) {
+  OmahaResponseHandlerAction *response_action =
+      new OmahaResponseHandlerAction(&fake_system_state_);
+  response_action->install_plan_.is_resume = true;
+  attempter_.response_handler_action_.reset(response_action);
+  MockAction action;
+  const ErrorCode kCode = ErrorCode::kInstallDeviceOpenError;
+  attempter_.CreatePendingErrorEvent(&action, kCode);
+  ASSERT_NE(nullptr, attempter_.error_event_.get());
+  EXPECT_EQ(OmahaEvent::kTypeUpdateComplete, attempter_.error_event_->type);
+  EXPECT_EQ(OmahaEvent::kResultError, attempter_.error_event_->result);
+  EXPECT_EQ(
+      static_cast<ErrorCode>(
+          static_cast<int>(kCode) |
+          static_cast<int>(ErrorCode::kResumedFlag) |
+          static_cast<int>(ErrorCode::kTestOmahaUrlFlag)),
+      attempter_.error_event_->error_code);
+}
+
+TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenNotEnabled) {
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  mock_p2p_manager.fake().SetP2PEnabled(false);
+  EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0);
+  attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PNotStartedAtStartupWhenEnabledButNotSharing) {
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  mock_p2p_manager.fake().SetP2PEnabled(true);
+  EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning()).Times(0);
+  attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PStartedAtStartupWhenEnabledAndSharing) {
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  mock_p2p_manager.fake().SetP2PEnabled(true);
+  mock_p2p_manager.fake().SetCountSharedFilesResult(1);
+  EXPECT_CALL(mock_p2p_manager, EnsureP2PRunning());
+  attempter_.UpdateEngineStarted();
+}
+
+TEST_F(UpdateAttempterTest, P2PNotEnabled) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::P2PNotEnabledStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::P2PNotEnabledStart() {
+  // If P2P is not enabled, check that we do not attempt housekeeping
+  // and do not convey that p2p is to be used.
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  mock_p2p_manager.fake().SetP2PEnabled(false);
+  EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
+  attempter_.Update("", "", "", "", false, false);
+  EXPECT_FALSE(actual_using_p2p_for_downloading_);
+  EXPECT_FALSE(actual_using_p2p_for_sharing());
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledStartingFails) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::P2PEnabledStartingFailsStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledStartingFailsStart() {
+  // If p2p is enabled, but starting it fails ensure we don't do
+  // any housekeeping and do not convey that p2p should be used.
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  mock_p2p_manager.fake().SetP2PEnabled(true);
+  mock_p2p_manager.fake().SetEnsureP2PRunningResult(false);
+  mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
+  EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
+  attempter_.Update("", "", "", "", false, false);
+  EXPECT_FALSE(actual_using_p2p_for_downloading());
+  EXPECT_FALSE(actual_using_p2p_for_sharing());
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledHousekeepingFails) {
+  loop_.PostTask(
+      FROM_HERE,
+      base::Bind(&UpdateAttempterTest::P2PEnabledHousekeepingFailsStart,
+                 base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledHousekeepingFailsStart() {
+  // If p2p is enabled, starting it works but housekeeping fails, ensure
+  // we do not convey p2p is to be used.
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  mock_p2p_manager.fake().SetP2PEnabled(true);
+  mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+  mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
+  EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+  attempter_.Update("", "", "", "", false, false);
+  EXPECT_FALSE(actual_using_p2p_for_downloading());
+  EXPECT_FALSE(actual_using_p2p_for_sharing());
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabled) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::P2PEnabledStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledStart() {
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  // If P2P is enabled and starting it works, check that we performed
+  // housekeeping and that we convey p2p should be used.
+  mock_p2p_manager.fake().SetP2PEnabled(true);
+  mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+  mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
+  EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+  attempter_.Update("", "", "", "", false, false);
+  EXPECT_TRUE(actual_using_p2p_for_downloading());
+  EXPECT_TRUE(actual_using_p2p_for_sharing());
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, P2PEnabledInteractive) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::P2PEnabledInteractiveStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::P2PEnabledInteractiveStart() {
+  MockP2PManager mock_p2p_manager;
+  fake_system_state_.set_p2p_manager(&mock_p2p_manager);
+  // For an interactive check, if P2P is enabled and starting it
+  // works, check that we performed housekeeping and that we convey
+  // p2p should be used for sharing but NOT for downloading.
+  mock_p2p_manager.fake().SetP2PEnabled(true);
+  mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
+  mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
+  EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
+  attempter_.Update("", "", "", "", false, true /* interactive */);
+  EXPECT_FALSE(actual_using_p2p_for_downloading());
+  EXPECT_TRUE(actual_using_p2p_for_sharing());
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, ReadScatterFactorFromPolicy) {
+  loop_.PostTask(
+      FROM_HERE,
+      base::Bind(&UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart,
+                 base::Unretained(this)));
+  loop_.Run();
+}
+
+// Tests that the scatter_factor_in_seconds value is properly fetched
+// from the device policy.
+void UpdateAttempterTest::ReadScatterFactorFromPolicyTestStart() {
+  int64_t scatter_factor_in_seconds = 36000;
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy);
+
+  EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(scatter_factor_in_seconds),
+          Return(true)));
+
+  attempter_.Update("", "", "", "", false, false);
+  EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, DecrementUpdateCheckCountTest) {
+  loop_.PostTask(
+      FROM_HERE,
+      base::Bind(&UpdateAttempterTest::DecrementUpdateCheckCountTestStart,
+                 base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::DecrementUpdateCheckCountTestStart() {
+  // Tests that the scatter_factor_in_seconds value is properly fetched
+  // from the device policy and is decremented if value > 0.
+  int64_t initial_value = 5;
+  FakePrefs fake_prefs;
+  attempter_.prefs_ = &fake_prefs;
+
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+
+  EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+
+  int64_t scatter_factor_in_seconds = 10;
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy);
+
+  EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(scatter_factor_in_seconds),
+          Return(true)));
+
+  attempter_.Update("", "", "", "", false, false);
+  EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+  // Make sure the file still exists.
+  EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+
+  int64_t new_value;
+  EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
+  EXPECT_EQ(initial_value - 1, new_value);
+
+  EXPECT_TRUE(
+      attempter_.omaha_request_params_->update_check_count_wait_enabled());
+
+  // However, if the count is already 0, it's not decremented. Test that.
+  initial_value = 0;
+  EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+  attempter_.Update("", "", "", "", false, false);
+  EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+  EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
+  EXPECT_EQ(initial_value, new_value);
+
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, NoScatteringDoneDuringManualUpdateTestStart) {
+  loop_.PostTask(FROM_HERE, base::Bind(
+      &UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart,
+      base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::NoScatteringDoneDuringManualUpdateTestStart() {
+  // Tests that no scattering logic is enabled if the update check
+  // is manually done (as opposed to a scheduled update check)
+  int64_t initial_value = 8;
+  FakePrefs fake_prefs;
+  attempter_.prefs_ = &fake_prefs;
+
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+  fake_system_state_.set_prefs(&fake_prefs);
+
+  EXPECT_TRUE(fake_prefs.SetInt64(kPrefsWallClockWaitPeriod, initial_value));
+  EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
+
+  // make sure scatter_factor is non-zero as scattering is disabled
+  // otherwise.
+  int64_t scatter_factor_in_seconds = 50;
+
+  policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+  attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy);
+
+  EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+      .WillRepeatedly(DoAll(
+          SetArgumentPointee<0>(scatter_factor_in_seconds),
+          Return(true)));
+
+  // Trigger an interactive check so we can test that scattering is disabled.
+  attempter_.Update("", "", "", "", false, true);
+  EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
+
+  // Make sure scattering is disabled for manual (i.e. user initiated) update
+  // checks and all artifacts are removed.
+  EXPECT_FALSE(
+      attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockWaitPeriod));
+  EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InSeconds());
+  EXPECT_FALSE(
+      attempter_.omaha_request_params_->update_check_count_wait_enabled());
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsUpdateCheckCount));
+
+  ScheduleQuitMainLoop();
+}
+
+// Checks that we only report daily metrics at most every 24 hours.
+TEST_F(UpdateAttempterTest, ReportDailyMetrics) {
+  FakeClock fake_clock;
+  FakePrefs fake_prefs;
+
+  fake_system_state_.set_clock(&fake_clock);
+  fake_system_state_.set_prefs(&fake_prefs);
+
+  Time epoch = Time::FromInternalValue(0);
+  fake_clock.SetWallclockTime(epoch);
+
+  // If there is no kPrefsDailyMetricsLastReportedAt state variable,
+  // we should report.
+  EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+  // We should not report again if no time has passed.
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+  // We should not report if only 10 hours has passed.
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(10));
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+  // We should not report if only 24 hours - 1 sec has passed.
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24) -
+                              TimeDelta::FromSeconds(1));
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+  // We should report if 24 hours has passed.
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(24));
+  EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+
+  // But then we should not report again..
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+  // .. until another 24 hours has passed
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(47));
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(48));
+  EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+  // .. and another 24 hours
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71));
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(72));
+  EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+  // If the span between time of reporting and present time is
+  // negative, we report. This is in order to reset the timestamp and
+  // avoid an edge condition whereby a distant point in the future is
+  // in the state variable resulting in us never ever reporting again.
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(71));
+  EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+
+  // In this case we should not update until the clock reads 71 + 24 = 95.
+  // Check that.
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(94));
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+  fake_clock.SetWallclockTime(epoch + TimeDelta::FromHours(95));
+  EXPECT_TRUE(attempter_.CheckAndReportDailyMetrics());
+  EXPECT_FALSE(attempter_.CheckAndReportDailyMetrics());
+}
+
+TEST_F(UpdateAttempterTest, BootTimeInUpdateMarkerFile) {
+  const string update_completed_marker = test_dir_ + "/update-completed-marker";
+  UpdateAttempterUnderTest attempter(&fake_system_state_, &dbus_,
+                                     update_completed_marker);
+
+  FakeClock fake_clock;
+  fake_clock.SetBootTime(Time::FromTimeT(42));
+  fake_system_state_.set_clock(&fake_clock);
+
+  Time boot_time;
+  EXPECT_FALSE(attempter.GetBootTimeAtUpdate(&boot_time));
+
+  attempter.WriteUpdateCompletedMarker();
+
+  EXPECT_TRUE(attempter.GetBootTimeAtUpdate(&boot_time));
+  EXPECT_EQ(boot_time.ToTimeT(), 42);
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedUnofficial) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+  EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedOfficialDevmode) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+  EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+                                   StrEq(debugd::kQueryDevFeatures),
+                                   _, A<gint*>()))
+      .WillRepeatedly(DoAll(SetArgumentPointee<3>(0),
+                            Return(true)));
+  EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedOfficialNormal) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+  // debugd should not be queried in this case.
+  EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+                                   StrEq(debugd::kQueryDevFeatures),
+                                   _, A<gint*>()))
+      .Times(0);
+  EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedDebugdDisabled) {
+  using debugd::DEV_FEATURES_DISABLED;
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+  EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+                                   StrEq(debugd::kQueryDevFeatures),
+                                   _, A<gint*>()))
+      .WillRepeatedly(DoAll(SetArgumentPointee<3>(DEV_FEATURES_DISABLED),
+                            Return(true)));
+  EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedDebugdFailure) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+  EXPECT_CALL(dbus_, ProxyCall_0_1(fake_dbus_debugd_proxy_,
+                                   StrEq(debugd::kQueryDevFeatures),
+                                   _, A<gint*>()))
+      .WillRepeatedly(Return(false));
+  EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+  attempter_.CheckForUpdate("", "autest", true);
+  EXPECT_EQ(chromeos_update_engine::kAUTestOmahaUrl,
+            attempter_.forced_omaha_url());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateScheduledAUTest) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+  attempter_.CheckForUpdate("", "autest-scheduled", true);
+  EXPECT_EQ(chromeos_update_engine::kAUTestOmahaUrl,
+            attempter_.forced_omaha_url());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine.conf b/update_engine.conf
new file mode 100644
index 0000000..dc65ebe
--- /dev/null
+++ b/update_engine.conf
@@ -0,0 +1 @@
+PAYLOAD_MINOR_VERSION=2
diff --git a/update_engine.gyp b/update_engine.gyp
new file mode 100644
index 0000000..d3232a3
--- /dev/null
+++ b/update_engine.gyp
@@ -0,0 +1,432 @@
+{
+  'target_defaults': {
+    'variables': {
+      'deps': [
+        'libchrome-<(libbase_ver)',
+        'libchromeos-<(libbase_ver)',
+      ],
+      # Defaults if the -DUSE_* flags are not passed to gyp is 0. You can set
+      # the default value for the USE flag in the ebuild.
+      'USE_hwid_override%': '0',
+      'USE_power_management%': '0',
+    },
+    'cflags': [
+      '-g',
+      '-ffunction-sections',
+      '-Wall',
+      '-Wextra',
+      '-Werror',
+      '-Wno-unused-parameter',
+      '-Wno-deprecated-register',
+    ],
+    'cflags_cc': [
+      '-fno-strict-aliasing',
+      '-Wnon-virtual-dtor',
+    ],
+    'ldflags': [
+      '-Wl,--gc-sections',
+    ],
+    'defines': [
+      '_POSIX_C_SOURCE=199309L',
+      'USE_HWID_OVERRIDE=<(USE_hwid_override)',
+      'USE_MTD=<(USE_mtd)',
+      'USE_POWER_MANAGEMENT=<(USE_power_management)',
+    ],
+  },
+  'targets': [
+    # Protobufs.
+    {
+      'target_name': 'update_metadata-protos',
+      'type': 'static_library',
+      'variables': {
+        'proto_in_dir': '.',
+        'proto_out_dir': 'include/update_engine',
+        'exported_deps': [
+          'protobuf-lite',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'sources': [
+        'update_metadata.proto'
+      ],
+      'includes': ['../common-mk/protoc.gypi'],
+    },
+    # D-Bus glib bindings.
+    {
+      'target_name': 'update_engine-dbus-server',
+      'type': 'none',
+      'variables': {
+        'dbus_glib_type': 'server',
+        'dbus_glib_out_dir': 'include/update_engine',
+        'dbus_glib_prefix': 'update_engine_service',
+      },
+      'sources': [
+        'dbus_bindings/org.chromium.UpdateEngineInterface.xml',
+      ],
+      'includes': ['../common-mk/dbus_glib.gypi'],
+    },
+    # The main static_library with all the code.
+    {
+      'target_name': 'libupdate_engine',
+      'type': 'static_library',
+      'dependencies': [
+        'update_metadata-protos',
+      ],
+      'variables': {
+        'exported_deps': [
+          'dbus-1',
+          'dbus-glib-1',
+          'glib-2.0',
+          'gthread-2.0',
+          'libchrome-<(libbase_ver)',
+          'libchromeos-<(libbase_ver)',
+          'libcrypto',
+          'libcurl',
+          'libmetrics-<(libbase_ver)',
+          'libssl',
+          'expat'
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'link_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+        'libraries': [
+          '-lbz2',
+          '-lpolicy-<(libbase_ver)',
+          '-lrootdev',
+          '-lrt',
+          '-lvboot_host',
+        ],
+      },
+      'sources': [
+        'action_processor.cc',
+        'bzip.cc',
+        'bzip_extent_writer.cc',
+        'certificate_checker.cc',
+        'chrome_browser_proxy_resolver.cc',
+        'clock.cc',
+        'connection_manager.cc',
+        'constants.cc',
+        'dbus_service.cc',
+        'delta_performer.cc',
+        'download_action.cc',
+        'extent_writer.cc',
+        'file_descriptor.cc',
+        'file_writer.cc',
+        'filesystem_verifier_action.cc',
+        'glib_utils.cc',
+        'hardware.cc',
+        'http_common.cc',
+        'http_fetcher.cc',
+        'hwid_override.cc',
+        'install_plan.cc',
+        'libcurl_http_fetcher.cc',
+        'metrics.cc',
+        'multi_range_http_fetcher.cc',
+        'omaha_hash_calculator.cc',
+        'omaha_request_action.cc',
+        'omaha_request_params.cc',
+        'omaha_response_handler_action.cc',
+        'p2p_manager.cc',
+        'payload_constants.cc',
+        'payload_state.cc',
+        'payload_verifier.cc',
+        'postinstall_runner_action.cc',
+        'prefs.cc',
+        'proxy_resolver.cc',
+        'real_system_state.cc',
+        'subprocess.cc',
+        'terminator.cc',
+        'update_attempter.cc',
+        'update_manager/boxed_value.cc',
+        'update_manager/chromeos_policy.cc',
+        'update_manager/default_policy.cc',
+        'update_manager/evaluation_context.cc',
+        'update_manager/policy.cc',
+        'update_manager/real_config_provider.cc',
+        'update_manager/real_device_policy_provider.cc',
+        'update_manager/real_random_provider.cc',
+        'update_manager/real_shill_provider.cc',
+        'update_manager/real_system_provider.cc',
+        'update_manager/real_time_provider.cc',
+        'update_manager/real_updater_provider.cc',
+        'update_manager/state_factory.cc',
+        'update_manager/update_manager.cc',
+        'utils.cc',
+      ],
+      'conditions': [
+        ['USE_mtd == 1', {
+          'sources': [
+            'mtd_file_descriptor.cc',
+          ],
+          'link_settings': {
+            'libraries': [
+              '-lmtdutils',
+            ],
+          },
+        }],
+      ],
+    },
+    # update_engine daemon.
+    {
+      'target_name': 'update_engine',
+      'type': 'executable',
+      'dependencies': [
+        'libupdate_engine',
+        'update_engine-dbus-server',
+      ],
+      'sources': [
+        'main.cc',
+      ]
+    },
+    # update_engine console client.
+    {
+      'target_name': 'update_engine_client',
+      'type': 'executable',
+      'variables': {
+        'exported_deps': [
+          'libchrome-<(libbase_ver)',
+          'libchromeos-<(libbase_ver)',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'link_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'sources': [
+        'update_engine_client.cc',
+      ],
+      'actions': [
+        {
+          'action_name': 'update_engine-dbus-proxies',
+          'variables': {
+            'dbus_service_config': 'dbus_bindings/dbus-service-config.json',
+            'proxy_output_file': 'include/update_engine/dbus_proxies.h'
+          },
+          'sources': [
+            'dbus_bindings/org.chromium.UpdateEngineInterface.xml',
+          ],
+          'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+        },
+      ]
+    },
+    # server-side code. This is used for delta_generator and unittests but not
+    # for any client code.
+    {
+      'target_name': 'libpayload_generator',
+      'type': 'static_library',
+      'dependencies': [
+        'update_metadata-protos',
+      ],
+      'variables': {
+        'exported_deps': [
+          'ext2fs',
+        ],
+        'deps': ['<@(exported_deps)'],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'link_settings': {
+        'variables': {
+          'deps': [
+            '<@(exported_deps)',
+          ],
+        },
+        'libraries': [
+          '-lvboot_host',
+        ],
+      },
+      'sources': [
+        'payload_generator/ab_generator.cc',
+        'payload_generator/annotated_operation.cc',
+        'payload_generator/cycle_breaker.cc',
+        'payload_generator/delta_diff_generator.cc',
+        'payload_generator/delta_diff_utils.cc',
+        'payload_generator/ext2_filesystem.cc',
+        'payload_generator/extent_ranges.cc',
+        'payload_generator/extent_utils.cc',
+        'payload_generator/full_update_generator.cc',
+        'payload_generator/graph_types.cc',
+        'payload_generator/graph_utils.cc',
+        'payload_generator/inplace_generator.cc',
+        'payload_generator/payload_file.cc',
+        'payload_generator/payload_generation_config.cc',
+        'payload_generator/payload_signer.cc',
+        'payload_generator/raw_filesystem.cc',
+        'payload_generator/tarjan.cc',
+        'payload_generator/topological_sort.cc',
+        'payload_generator/verity_utils.cc',
+      ],
+    },
+    # server-side delta generator.
+    {
+      'target_name': 'delta_generator',
+      'type': 'executable',
+      'dependencies': [
+        'libupdate_engine',
+        'libpayload_generator',
+      ],
+      'link_settings': {
+        'ldflags!': [
+          '-pie',
+        ],
+      },
+      'sources': [
+        'payload_generator/generate_delta_main.cc',
+      ]
+    },
+  ],
+  'conditions': [
+    ['USE_test == 1', {
+      'targets': [
+        # Public keys used for unit testing.
+        {
+          'target_name': 'update_engine-testkeys',
+          'type': 'none',
+          'variables': {
+            'openssl_pem_in_dir': '.',
+            'openssl_pem_out_dir': 'include/update_engine',
+          },
+          'sources': [
+            'unittest_key.pem',
+            'unittest_key2.pem',
+          ],
+          'includes': ['../common-mk/openssl_pem.gypi'],
+        },
+        # Sample images used for testing.
+        {
+          'target_name': 'update_engine-test_images',
+          'type': 'none',
+          'variables': {
+            'image_out_dir': '.',
+          },
+          'sources': [
+            'sample_images/disk_ext2_1k.txt',
+            'sample_images/disk_ext2_4k.txt',
+            'sample_images/disk_ext2_ue_settings.txt',
+          ],
+          'includes': ['generate_image.gypi'],
+        },
+        # Test HTTP Server.
+        {
+          'target_name': 'test_http_server',
+          'type': 'executable',
+          'dependencies': ['libupdate_engine'],
+          'sources': [
+            'test_http_server.cc',
+          ]
+        },
+        # Main unittest file.
+        {
+          'target_name': 'update_engine_unittests',
+          'type': 'executable',
+          'includes': ['../common-mk/common_test.gypi'],
+          'variables': {
+            'deps': [
+              'libchromeos-test-<(libbase_ver)',
+              'libchrome-test-<(libbase_ver)',
+            ],
+          },
+          'dependencies': [
+            'libupdate_engine',
+            'libpayload_generator',
+          ],
+          'includes': ['../common-mk/common_test.gypi'],
+          'sources': [
+            'action_pipe_unittest.cc',
+            'action_processor_unittest.cc',
+            'action_unittest.cc',
+            'bzip_extent_writer_unittest.cc',
+            'certificate_checker_unittest.cc',
+            'chrome_browser_proxy_resolver_unittest.cc',
+            'connection_manager_unittest.cc',
+            'delta_performer_unittest.cc',
+            'download_action_unittest.cc',
+            'extent_writer_unittest.cc',
+            'fake_prefs.cc',
+            'fake_system_state.cc',
+            'file_writer_unittest.cc',
+            'filesystem_verifier_action_unittest.cc',
+            'http_fetcher_unittest.cc',
+            'hwid_override_unittest.cc',
+            'mock_http_fetcher.cc',
+            'omaha_hash_calculator_unittest.cc',
+            'omaha_request_action_unittest.cc',
+            'omaha_request_params_unittest.cc',
+            'omaha_response_handler_action_unittest.cc',
+            'p2p_manager_unittest.cc',
+            'payload_generator/ab_generator_unittest.cc',
+            'payload_generator/cycle_breaker_unittest.cc',
+            'payload_generator/delta_diff_utils_unittest.cc',
+            'payload_generator/ext2_filesystem_unittest.cc',
+            'payload_generator/extent_ranges_unittest.cc',
+            'payload_generator/extent_utils_unittest.cc',
+            'payload_generator/fake_filesystem.cc',
+            'payload_generator/full_update_generator_unittest.cc',
+            'payload_generator/graph_utils_unittest.cc',
+            'payload_generator/inplace_generator_unittest.cc',
+            'payload_generator/payload_signer_unittest.cc',
+            'payload_generator/payload_file_unittest.cc',
+            'payload_generator/tarjan_unittest.cc',
+            'payload_generator/topological_sort_unittest.cc',
+            'payload_generator/verity_utils_unittest.cc',
+            'payload_state_unittest.cc',
+            'postinstall_runner_action_unittest.cc',
+            'prefs_unittest.cc',
+            'subprocess_unittest.cc',
+            'terminator_unittest.cc',
+            'test_utils.cc',
+            'test_utils_unittest.cc',
+            'update_attempter_unittest.cc',
+            'update_manager/boxed_value_unittest.cc',
+            'update_manager/chromeos_policy_unittest.cc',
+            'update_manager/evaluation_context_unittest.cc',
+            'update_manager/generic_variables_unittest.cc',
+            'update_manager/prng_unittest.cc',
+            'update_manager/real_config_provider_unittest.cc',
+            'update_manager/real_device_policy_provider_unittest.cc',
+            'update_manager/real_random_provider_unittest.cc',
+            'update_manager/real_shill_provider_unittest.cc',
+            'update_manager/real_system_provider_unittest.cc',
+            'update_manager/real_time_provider_unittest.cc',
+            'update_manager/real_updater_provider_unittest.cc',
+            'update_manager/umtest_utils.cc',
+            'update_manager/update_manager_unittest.cc',
+            'update_manager/variable_unittest.cc',
+            'utils_unittest.cc',
+            'zip_unittest.cc',
+            # Main entry point for runnning tests.
+            'testrunner.cc',
+          ],
+        },
+      ],
+    }],
+  ],
+}
diff --git a/update_engine_client.cc b/update_engine_client.cc
new file mode 100644
index 0000000..d0fee7a
--- /dev/null
+++ b/update_engine_client.cc
@@ -0,0 +1,658 @@
+// Copyright (c) 2012 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 <inttypes.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/flag_helper.h>
+#include <dbus/bus.h>
+
+#include "update_engine/dbus_constants.h"
+#include "update_engine/dbus_proxies.h"
+
+using chromeos_update_engine::kAttemptUpdateFlagNonInteractive;
+using chromeos_update_engine::kUpdateEngineServiceName;
+using std::string;
+
+namespace {
+
+// Constant to signal that we need to continue running the daemon after
+// initialization.
+const int kContinueRunning = -1;
+
+class UpdateEngineClient : public chromeos::DBusDaemon {
+ public:
+  UpdateEngineClient(int argc, char** argv) : argc_(argc), argv_(argv) {}
+  ~UpdateEngineClient() override = default;
+
+ protected:
+  int OnInit() override {
+    int ret = DBusDaemon::OnInit();
+    if (ret != EX_OK)
+      return ret;
+    if (!InitProxy())
+      return 1;
+    // Wait for the UpdateEngine to be available or timeout.
+    proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
+        base::Bind(&UpdateEngineClient::OnServiceAvailable,
+                   base::Unretained(this)));
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&UpdateEngineClient::OnServiceAvailableTimeout,
+                   base::Unretained(this)),
+        base::TimeDelta::FromSeconds(10));
+    return EX_OK;
+  }
+
+ private:
+  bool InitProxy();
+
+  // Callback called when the UpdateEngine service becomes available.
+  void OnServiceAvailable(bool service_is_available);
+
+  // Callback called when the UpdateEngine service doesn't become available
+  // after a timeout.
+  void OnServiceAvailableTimeout();
+
+
+  // Callback called when a StatusUpdate signal is received.
+  void OnStatusUpdateSignal(int64_t last_checked_time,
+                            double progress,
+                            const string& current_operation,
+                            const string& new_version,
+                            int64_t new_size);
+  // Callback called when the OnStatusUpdateSignal() handler is registered.
+  void OnStatusUpdateSignalRegistration(const string& interface,
+                                        const string& signal_name,
+                                        bool success);
+
+  // Registers a callback that prints on stderr the received StatusUpdate
+  // signals.
+  // The daemon should continue running for this to work.
+  void WatchForUpdates();
+
+  void ResetStatus();
+
+  // Show the status of the update engine in stdout.
+  // Blocking call. Exits the program with error 1 in case of an error.
+  void ShowStatus();
+
+  // Return the current operation status, such as UPDATE_STATUS_IDLE.
+  // Blocking call. Exits the program with error 1 in case of an error.
+  string GetCurrentOperation();
+
+  void Rollback(bool rollback);
+  string GetRollbackPartition();
+  string GetKernelDevices();
+  void CheckForUpdates(const string& app_version,
+                       const string& omaha_url,
+                       bool interactive);
+
+  // Reboot the device if a reboot is needed.
+  // Blocking call. Ignores failures.
+  void RebootIfNeeded();
+
+  // Getter and setter for the target channel. If |get_current_channel| is true,
+  // the current channel instead of the target channel will be returned.
+  // Blocking call. Exits the program with error 1 in case of an error.
+  void SetTargetChannel(const string& target_channel, bool allow_powerwash);
+  string GetChannel(bool get_current_channel);
+
+  // Getter and setter for the updates over cellular connections.
+  // Blocking call. Exits the program with error 1 in case of an error.
+  void SetUpdateOverCellularPermission(bool allowed);
+  bool GetUpdateOverCellularPermission();
+
+  // Getter and setter for the updates from P2P permission.
+  // Blocking call. Exits the program with error 1 in case of an error.
+  void SetP2PUpdatePermission(bool enabled);
+  bool GetP2PUpdatePermission();
+
+  // This is similar to watching for updates but rather than registering
+  // a signal watch, actively poll the daemon just in case it stops
+  // sending notifications.
+  void WaitForUpdateComplete();
+  void OnUpdateCompleteCheck(int64_t last_checked_time,
+                             double progress,
+                             const string& current_operation,
+                             const string& new_version,
+                             int64_t new_size);
+
+  // Blocking call. Exits the program with error 1 in case of an error.
+  void ShowPrevVersion();
+
+  // Returns whether the current status is such that a reboot is needed.
+  // Blocking call. Exits the program with error 1 in case of an error.
+  bool GetIsRebootNeeded();
+
+  // Blocks until a reboot is needed. If the reboot is needed, exits the program
+  // with 0. Otherwise it exits the program with 1 if an error occurs before
+  // the reboot is needed.
+  void WaitForRebootNeeded();
+  void OnRebootNeededCheck(int64_t last_checked_time,
+                           double progress,
+                           const string& current_operation,
+                           const string& new_version,
+                           int64_t new_size);
+  // Callback called when the OnRebootNeededCheck() handler is registered. This
+  // is useful to check at this point if the reboot is needed, without loosing
+  // any StatusUpdate signals and avoiding the race condition.
+  void OnRebootNeededCheckRegistration(const string& interface,
+                                       const string& signal_name,
+                                       bool success);
+
+  // Main method that parses and triggers all the actions based on the passed
+  // flags.
+  int ProcessFlags();
+
+  // DBus Proxy to the update_engine daemon object used for all the calls.
+  std::unique_ptr<org::chromium::UpdateEngineInterfaceProxy> proxy_;
+
+  // Copy of argc and argv passed to main().
+  int argc_;
+  char** argv_;
+
+  // Tell whether the UpdateEngine service is available after startup.
+  bool service_is_available_{false};
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateEngineClient);
+};
+
+bool UpdateEngineClient::InitProxy() {
+  proxy_.reset(new org::chromium::UpdateEngineInterfaceProxy(bus_));
+
+  if (!proxy_->GetObjectProxy()) {
+    LOG(ERROR) << "Error getting dbus proxy for " << kUpdateEngineServiceName;
+    return false;
+  }
+  return true;
+}
+
+void UpdateEngineClient::OnServiceAvailable(bool service_is_available) {
+  service_is_available_ = service_is_available;
+  if (!service_is_available) {
+    LOG(ERROR) << "UpdateEngineService not available.";
+    QuitWithExitCode(-1);
+  }
+  int ret = ProcessFlags();
+  if (ret != kContinueRunning)
+    QuitWithExitCode(ret);
+}
+
+void UpdateEngineClient::OnServiceAvailableTimeout() {
+  if (!service_is_available_) {
+    LOG(ERROR) << "Waiting for UpdateEngineService timeout. Is update_engine "
+                  "daemon running?";
+    QuitWithExitCode(-1);
+  }
+}
+
+void UpdateEngineClient::OnStatusUpdateSignal(
+    int64_t last_checked_time,
+    double progress,
+    const string& current_operation,
+    const string& new_version,
+    int64_t new_size) {
+  LOG(INFO) << "Got status update:";
+  LOG(INFO) << "  last_checked_time: " << last_checked_time;
+  LOG(INFO) << "  progress: " << progress;
+  LOG(INFO) << "  current_operation: " << current_operation;
+  LOG(INFO) << "  new_version: " << new_version;
+  LOG(INFO) << "  new_size: " << new_size;
+}
+
+void UpdateEngineClient::OnStatusUpdateSignalRegistration(
+    const string& interface,
+    const string& signal_name,
+    bool success) {
+  VLOG(1) << "OnStatusUpdateSignalRegistration(" << interface << ", "
+          << signal_name << ", " << success << ");";
+  if (!success) {
+    LOG(ERROR) << "Couldn't connect to the " << signal_name << " signal.";
+    exit(1);
+  }
+}
+
+void UpdateEngineClient::WatchForUpdates() {
+  proxy_->RegisterStatusUpdateSignalHandler(
+      base::Bind(&UpdateEngineClient::OnStatusUpdateSignal,
+                 base::Unretained(this)),
+      base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+                 base::Unretained(this)));
+}
+
+void UpdateEngineClient::ResetStatus() {
+  bool ret = proxy_->ResetStatus(nullptr);
+  CHECK(ret) << "ResetStatus() failed.";
+}
+
+void UpdateEngineClient::ShowStatus() {
+  int64_t last_checked_time = 0;
+  double progress = 0.0;
+  string current_op;
+  string new_version;
+  int64_t new_size = 0;
+
+  bool ret = proxy_->GetStatus(
+      &last_checked_time, &progress, &current_op, &new_version, &new_size,
+      nullptr);
+  CHECK(ret) << "GetStatus() failed";
+  printf("LAST_CHECKED_TIME=%" PRIi64 "\nPROGRESS=%f\nCURRENT_OP=%s\n"
+         "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n",
+         last_checked_time,
+         progress,
+         current_op.c_str(),
+         new_version.c_str(),
+         new_size);
+}
+
+string UpdateEngineClient::GetCurrentOperation() {
+  int64_t last_checked_time = 0;
+  double progress = 0.0;
+  string current_op;
+  string new_version;
+  int64_t new_size = 0;
+
+  bool ret = proxy_->GetStatus(
+      &last_checked_time, &progress, &current_op, &new_version, &new_size,
+      nullptr);
+  CHECK(ret) << "GetStatus() failed";
+  return current_op;
+}
+
+void UpdateEngineClient::Rollback(bool rollback) {
+  bool ret = proxy_->AttemptRollback(rollback, nullptr);
+  CHECK(ret) << "Rollback request failed.";
+}
+
+string UpdateEngineClient::GetRollbackPartition() {
+  string rollback_partition;
+  bool ret = proxy_->GetRollbackPartition(&rollback_partition, nullptr);
+  CHECK(ret) << "Error while querying rollback partition availabilty.";
+  return rollback_partition;
+}
+
+string UpdateEngineClient::GetKernelDevices() {
+  string kernel_devices;
+  bool ret = proxy_->GetKernelDevices(&kernel_devices, nullptr);
+  CHECK(ret) << "Error while getting a list of kernel devices";
+  return kernel_devices;
+}
+
+void UpdateEngineClient::CheckForUpdates(const string& app_version,
+                                         const string& omaha_url,
+                                         bool interactive) {
+  int32_t flags = interactive ? 0 : kAttemptUpdateFlagNonInteractive;
+  bool ret = proxy_->AttemptUpdateWithFlags(app_version, omaha_url, flags,
+                                            nullptr);
+  CHECK(ret) << "Error checking for update.";
+}
+
+void UpdateEngineClient::RebootIfNeeded() {
+  bool ret = proxy_->RebootIfNeeded(nullptr);
+  if (!ret) {
+    // Reboot error code doesn't necessarily mean that a reboot
+    // failed. For example, D-Bus may be shutdown before we receive the
+    // result.
+    LOG(INFO) << "RebootIfNeeded() failure ignored.";
+  }
+}
+
+void UpdateEngineClient::SetTargetChannel(const string& target_channel,
+                                          bool allow_powerwash) {
+  bool ret = proxy_->SetChannel(target_channel, allow_powerwash, nullptr);
+  CHECK(ret) << "Error setting the channel.";
+  LOG(INFO) << "Channel permanently set to: " << target_channel;
+}
+
+string UpdateEngineClient::GetChannel(bool get_current_channel) {
+  string channel;
+  bool ret = proxy_->GetChannel(get_current_channel, &channel, nullptr);
+  CHECK(ret) << "Error getting the channel.";
+  return channel;
+}
+
+void UpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) {
+  bool ret = proxy_->SetUpdateOverCellularPermission(allowed, nullptr);
+  CHECK(ret) << "Error setting the update over cellular setting.";
+}
+
+bool UpdateEngineClient::GetUpdateOverCellularPermission() {
+  bool allowed;
+  bool ret = proxy_->GetUpdateOverCellularPermission(&allowed, nullptr);
+  CHECK(ret) << "Error getting the update over cellular setting.";
+  return allowed;
+}
+
+void UpdateEngineClient::SetP2PUpdatePermission(bool enabled) {
+  bool ret = proxy_->SetP2PUpdatePermission(enabled, nullptr);
+  CHECK(ret) << "Error setting the peer-to-peer update setting.";
+}
+
+bool UpdateEngineClient::GetP2PUpdatePermission() {
+  bool enabled;
+  bool ret = proxy_->GetP2PUpdatePermission(&enabled, nullptr);
+  CHECK(ret) << "Error getting the peer-to-peer update setting.";
+  return enabled;
+}
+
+void UpdateEngineClient::OnUpdateCompleteCheck(
+    int64_t /* last_checked_time */,
+    double /* progress */,
+    const string& current_operation,
+    const string& /* new_version */,
+    int64_t /* new_size */) {
+  if (current_operation == update_engine::kUpdateStatusIdle) {
+    LOG(ERROR) << "Update failed, current operations is " << current_operation;
+    exit(1);
+  }
+  if (current_operation == update_engine::kUpdateStatusUpdatedNeedReboot) {
+    LOG(INFO) << "Update succeeded -- reboot needed.";
+    exit(0);
+  }
+}
+
+void UpdateEngineClient::WaitForUpdateComplete() {
+  proxy_->RegisterStatusUpdateSignalHandler(
+      base::Bind(&UpdateEngineClient::OnUpdateCompleteCheck,
+                 base::Unretained(this)),
+      base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+                 base::Unretained(this)));
+}
+
+void UpdateEngineClient::ShowPrevVersion() {
+  string prev_version = nullptr;
+
+  bool ret = proxy_->GetPrevVersion(&prev_version, nullptr);;
+  if (!ret) {
+    LOG(ERROR) << "Error getting previous version.";
+  } else {
+    LOG(INFO) << "Previous version = " << prev_version;
+  }
+}
+
+bool UpdateEngineClient::GetIsRebootNeeded() {
+  return GetCurrentOperation() == update_engine::kUpdateStatusUpdatedNeedReboot;
+}
+
+void UpdateEngineClient::OnRebootNeededCheck(
+    int64_t /* last_checked_time */,
+    double /* progress */,
+    const string& current_operation,
+    const string& /* new_version */,
+    int64_t /* new_size */) {
+  if (current_operation == update_engine::kUpdateStatusUpdatedNeedReboot) {
+    LOG(INFO) << "Reboot needed.";
+    exit(0);
+  }
+}
+
+void UpdateEngineClient::OnRebootNeededCheckRegistration(
+    const string& interface,
+    const string& signal_name,
+    bool success) {
+  if (GetIsRebootNeeded())
+    exit(0);
+  if (!success) {
+    LOG(ERROR) << "Couldn't connect to the " << signal_name << " signal.";
+    exit(1);
+  }
+}
+
+// Blocks until a reboot is needed. Returns true if waiting succeeded,
+// false if an error occurred.
+void UpdateEngineClient::WaitForRebootNeeded() {
+  proxy_->RegisterStatusUpdateSignalHandler(
+      base::Bind(&UpdateEngineClient::OnUpdateCompleteCheck,
+                 base::Unretained(this)),
+      base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+                 base::Unretained(this)));
+  if (GetIsRebootNeeded())
+    exit(0);
+}
+
+int UpdateEngineClient::ProcessFlags() {
+  DEFINE_string(app_version, "", "Force the current app version.");
+  DEFINE_string(channel, "",
+                "Set the target channel. The device will be powerwashed if the "
+                "target channel is more stable than the current channel unless "
+                "--nopowerwash is specified.");
+  DEFINE_bool(check_for_update, false, "Initiate check for updates.");
+  DEFINE_bool(follow, false, "Wait for any update operations to complete."
+              "Exit status is 0 if the update succeeded, and 1 otherwise.");
+  DEFINE_bool(interactive, true, "Mark the update request as interactive.");
+  DEFINE_string(omaha_url, "", "The URL of the Omaha update server.");
+  DEFINE_string(p2p_update, "",
+                "Enables (\"yes\") or disables (\"no\") the peer-to-peer update"
+                " sharing.");
+  DEFINE_bool(powerwash, true, "When performing rollback or channel change, "
+              "do a powerwash or allow it respectively.");
+  DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
+  DEFINE_bool(is_reboot_needed, false, "Exit status 0 if reboot is needed, "
+              "2 if reboot is not needed or 1 if an error occurred.");
+  DEFINE_bool(block_until_reboot_is_needed, false, "Blocks until reboot is "
+              "needed. Returns non-zero exit status if an error occurred.");
+  DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
+  DEFINE_bool(rollback, false,
+              "Perform a rollback to the previous partition. The device will "
+              "be powerwashed unless --nopowerwash is specified.");
+  DEFINE_bool(can_rollback, false, "Shows whether rollback partition "
+              "is available.");
+  DEFINE_bool(show_channel, false, "Show the current and target channels.");
+  DEFINE_bool(show_p2p_update, false,
+              "Show the current setting for peer-to-peer update sharing.");
+  DEFINE_bool(show_update_over_cellular, false,
+              "Show the current setting for updates over cellular networks.");
+  DEFINE_bool(status, false, "Print the status to stdout.");
+  DEFINE_bool(update, false, "Forces an update and waits for it to complete. "
+              "Implies --follow.");
+  DEFINE_string(update_over_cellular, "",
+                "Enables (\"yes\") or disables (\"no\") the updates over "
+                "cellular networks.");
+  DEFINE_bool(watch_for_updates, false,
+              "Listen for status updates and print them to the screen.");
+  DEFINE_bool(prev_version, false,
+              "Show the previous OS version used before the update reboot.");
+  DEFINE_bool(show_kernels, false, "Show the list of kernel patritions and "
+              "whether each of them is bootable or not");
+
+  // Boilerplate init commands.
+  base::CommandLine::Init(argc_, argv_);
+  chromeos::FlagHelper::Init(argc_, argv_, "Chromium OS Update Engine Client");
+
+  // Ensure there are no positional arguments.
+  const std::vector<string> positional_args =
+      base::CommandLine::ForCurrentProcess()->GetArgs();
+  if (!positional_args.empty()) {
+    LOG(ERROR) << "Found a positional argument '" << positional_args.front()
+               << "'. If you want to pass a value to a flag, pass it as "
+                  "--flag=value.";
+    return 1;
+  }
+
+  // Update the status if requested.
+  if (FLAGS_reset_status) {
+    LOG(INFO) << "Setting Update Engine status to idle ...";
+    ResetStatus();
+    LOG(INFO) << "ResetStatus succeeded; to undo partition table changes run:\n"
+                 "(D=$(rootdev -d) P=$(rootdev -s); cgpt p -i$(($(echo ${P#$D} "
+                 "| sed 's/^[^0-9]*//')-1)) $D;)";
+  }
+
+  // Changes the current update over cellular network setting.
+  if (!FLAGS_update_over_cellular.empty()) {
+    bool allowed = FLAGS_update_over_cellular == "yes";
+    if (!allowed && FLAGS_update_over_cellular != "no") {
+      LOG(ERROR) << "Unknown option: \"" << FLAGS_update_over_cellular
+                 << "\". Please specify \"yes\" or \"no\".";
+    } else {
+      SetUpdateOverCellularPermission(allowed);
+    }
+  }
+
+  // Show the current update over cellular network setting.
+  if (FLAGS_show_update_over_cellular) {
+    bool allowed = GetUpdateOverCellularPermission();
+    LOG(INFO) << "Current update over cellular network setting: "
+              << (allowed ? "ENABLED" : "DISABLED");
+  }
+
+  if (!FLAGS_powerwash && !FLAGS_rollback && FLAGS_channel.empty()) {
+    LOG(ERROR) << "powerwash flag only makes sense rollback or channel change";
+    return 1;
+  }
+
+  // Change the P2P enabled setting.
+  if (!FLAGS_p2p_update.empty()) {
+    bool enabled = FLAGS_p2p_update == "yes";
+    if (!enabled && FLAGS_p2p_update != "no") {
+      LOG(ERROR) << "Unknown option: \"" << FLAGS_p2p_update
+                 << "\". Please specify \"yes\" or \"no\".";
+    } else {
+      SetP2PUpdatePermission(enabled);
+    }
+  }
+
+  // Show the rollback availability.
+  if (FLAGS_can_rollback) {
+    string rollback_partition = GetRollbackPartition();
+    bool can_rollback = true;
+    if (rollback_partition.empty()) {
+      rollback_partition = "UNAVAILABLE";
+      can_rollback = false;
+    } else {
+      rollback_partition = "AVAILABLE: " + rollback_partition;
+    }
+
+    LOG(INFO) << "Rollback partition: " << rollback_partition;
+    if (!can_rollback) {
+      return 1;
+    }
+  }
+
+  // Show the current P2P enabled setting.
+  if (FLAGS_show_p2p_update) {
+    bool enabled = GetP2PUpdatePermission();
+    LOG(INFO) << "Current update using P2P setting: "
+              << (enabled ? "ENABLED" : "DISABLED");
+  }
+
+  // First, update the target channel if requested.
+  if (!FLAGS_channel.empty())
+    SetTargetChannel(FLAGS_channel, FLAGS_powerwash);
+
+  // Show the current and target channels if requested.
+  if (FLAGS_show_channel) {
+    string current_channel = GetChannel(true);
+    LOG(INFO) << "Current Channel: " << current_channel;
+
+    string target_channel = GetChannel(false);
+    if (!target_channel.empty())
+      LOG(INFO) << "Target Channel (pending update): " << target_channel;
+  }
+
+  bool do_update_request = FLAGS_check_for_update | FLAGS_update |
+      !FLAGS_app_version.empty() | !FLAGS_omaha_url.empty();
+  if (FLAGS_update)
+    FLAGS_follow = true;
+
+  if (do_update_request && FLAGS_rollback) {
+    LOG(ERROR) << "Incompatible flags specified with rollback."
+               << "Rollback should not include update-related flags.";
+    return 1;
+  }
+
+  if (FLAGS_rollback) {
+    LOG(INFO) << "Requesting rollback.";
+    Rollback(FLAGS_powerwash);
+  }
+
+  // Initiate an update check, if necessary.
+  if (do_update_request) {
+    LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
+    string app_version = FLAGS_app_version;
+    if (FLAGS_update && app_version.empty()) {
+      app_version = "ForcedUpdate";
+      LOG(INFO) << "Forcing an update by setting app_version to ForcedUpdate.";
+    }
+    LOG(INFO) << "Initiating update check and install.";
+    CheckForUpdates(app_version, FLAGS_omaha_url, FLAGS_interactive);
+  }
+
+  // These final options are all mutually exclusive with one another.
+  if (FLAGS_follow + FLAGS_watch_for_updates + FLAGS_reboot +
+      FLAGS_status + FLAGS_is_reboot_needed +
+      FLAGS_block_until_reboot_is_needed > 1) {
+    LOG(ERROR) << "Multiple exclusive options selected. "
+               << "Select only one of --follow, --watch_for_updates, --reboot, "
+               << "--is_reboot_needed, --block_until_reboot_is_needed, "
+               << "or --status.";
+    return 1;
+  }
+
+  if (FLAGS_status) {
+    LOG(INFO) << "Querying Update Engine status...";
+    ShowStatus();
+    return 0;
+  }
+
+  if (FLAGS_follow) {
+    LOG(INFO) << "Waiting for update to complete.";
+    WaitForUpdateComplete();
+    return kContinueRunning;
+  }
+
+  if (FLAGS_watch_for_updates) {
+    LOG(INFO) << "Watching for status updates.";
+    WatchForUpdates();
+    return kContinueRunning;
+  }
+
+  if (FLAGS_reboot) {
+    LOG(INFO) << "Requesting a reboot...";
+    RebootIfNeeded();
+    return 0;
+  }
+
+  if (FLAGS_prev_version) {
+    ShowPrevVersion();
+  }
+
+  if (FLAGS_show_kernels) {
+    LOG(INFO) << "Kernel partitions:\n"
+              << GetKernelDevices();
+  }
+
+  if (FLAGS_is_reboot_needed) {
+    // In case of error GetIsRebootNeeded() will exit with 1.
+    if (GetIsRebootNeeded()) {
+      return 0;
+    } else {
+      return 2;
+    }
+  }
+
+  if (FLAGS_block_until_reboot_is_needed) {
+    WaitForRebootNeeded();
+    return kContinueRunning;
+  }
+
+  return 0;
+}
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  UpdateEngineClient client(argc, argv);
+  return client.Run();
+}
diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc
new file mode 100644
index 0000000..0acc8cd
--- /dev/null
+++ b/update_manager/boxed_value.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2014 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/update_manager/boxed_value.h"
+
+#include <stdint.h>
+
+#include <set>
+#include <string>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+#include "update_engine/utils.h"
+
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// Template instantiation for common types; used in BoxedValue::ToString().
+// Keep in sync with boxed_value_unitttest.cc.
+
+template<>
+string BoxedValue::ValuePrinter<string>(const void* value) {
+  const string* val = reinterpret_cast<const string*>(value);
+  return *val;
+}
+
+template<>
+string BoxedValue::ValuePrinter<int>(const void* value) {
+  const int* val = reinterpret_cast<const int*>(value);
+  return base::IntToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<unsigned int>(const void* value) {
+  const unsigned int* val = reinterpret_cast<const unsigned int*>(value);
+  return base::UintToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<int64_t>(const void* value) {
+  const int64_t* val = reinterpret_cast<const int64_t*>(value);
+  return base::Int64ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<uint64_t>(const void* value) {
+  const uint64_t* val =
+    reinterpret_cast<const uint64_t*>(value);
+  return base::Uint64ToString(static_cast<uint64_t>(*val));
+}
+
+template<>
+string BoxedValue::ValuePrinter<bool>(const void* value) {
+  const bool* val = reinterpret_cast<const bool*>(value);
+  return *val ? "true" : "false";
+}
+
+template<>
+string BoxedValue::ValuePrinter<double>(const void* value) {
+  const double* val = reinterpret_cast<const double*>(value);
+  return base::DoubleToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::Time>(const void* value) {
+  const base::Time* val = reinterpret_cast<const base::Time*>(value);
+  return chromeos_update_engine::utils::ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::TimeDelta>(const void* value) {
+  const base::TimeDelta* val = reinterpret_cast<const base::TimeDelta*>(value);
+  return chromeos_update_engine::utils::FormatTimeDelta(*val);
+}
+
+static string ConnectionTypeToString(ConnectionType type) {
+  switch (type) {
+    case ConnectionType::kEthernet:
+      return "Ethernet";
+    case ConnectionType::kWifi:
+      return "Wifi";
+    case ConnectionType::kWimax:
+      return "Wimax";
+    case ConnectionType::kBluetooth:
+      return "Bluetooth";
+    case ConnectionType::kCellular:
+      return "Cellular";
+    case ConnectionType::kUnknown:
+      return "Unknown";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionType>(const void* value) {
+  const ConnectionType* val = reinterpret_cast<const ConnectionType*>(value);
+  return ConnectionTypeToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<set<ConnectionType>>(const void* value) {
+  string ret = "";
+  const set<ConnectionType>* val =
+      reinterpret_cast<const set<ConnectionType>*>(value);
+  for (auto& it : *val) {
+    ConnectionType type = it;
+    if (ret.size() > 0)
+      ret += ",";
+    ret += ConnectionTypeToString(type);
+  }
+  return ret;
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionTethering>(const void* value) {
+  const ConnectionTethering* val =
+      reinterpret_cast<const ConnectionTethering*>(value);
+  switch (*val) {
+    case ConnectionTethering::kNotDetected:
+      return "Not Detected";
+    case ConnectionTethering::kSuspected:
+      return "Suspected";
+    case ConnectionTethering::kConfirmed:
+      return "Confirmed";
+    case ConnectionTethering::kUnknown:
+      return "Unknown";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<Stage>(const void* value) {
+  const Stage* val = reinterpret_cast<const Stage*>(value);
+  switch (*val) {
+    case Stage::kIdle:
+      return "Idle";
+    case Stage::kCheckingForUpdate:
+      return "Checking For Update";
+    case Stage::kUpdateAvailable:
+      return "Update Available";
+    case Stage::kDownloading:
+      return "Downloading";
+    case Stage::kVerifying:
+      return "Verifying";
+    case Stage::kFinalizing:
+      return "Finalizing";
+    case Stage::kUpdatedNeedReboot:
+      return "Updated, Need Reboot";
+    case Stage::kReportingErrorEvent:
+      return "Reporting Error Event";
+    case Stage::kAttemptingRollback:
+      return "Attempting Rollback";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<UpdateRequestStatus>(const void* value) {
+  const UpdateRequestStatus* val =
+      reinterpret_cast<const UpdateRequestStatus*>(value);
+  switch (*val) {
+    case UpdateRequestStatus::kNone:
+      return "None";
+    case UpdateRequestStatus::kInteractive:
+      return "Interactive";
+    case UpdateRequestStatus::kPeriodic:
+      return "Periodic";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/boxed_value.h b/update_manager/boxed_value.h
new file mode 100644
index 0000000..200464f
--- /dev/null
+++ b/update_manager/boxed_value.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_BOXED_VALUE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// BoxedValue is a class to hold pointers of a given type that deletes them when
+// the instance goes out of scope, as std::unique_ptr<T> does. The main
+// difference with it is that the type T is not part of the class, i.e., this
+// isn't a parametric class. The class has a parametric constructor that accepts
+// a const T* which will define the type of the object passed on delete.
+//
+// It is safe to use this class in linked containers such as std::list and
+// std::map but the object can't be copied. This means that you need to
+// construct the BoxedValue in place using a container method like emplace()
+// or move it with std::move().
+//
+//   list<BoxedValue> lst;
+//   lst.emplace_back(new const int(42));
+//   lst.emplace_back(new const string("Hello world!"));
+//
+//   map<int, BoxedValue> m;
+//   m.emplace(123, std::move(BoxedValue(new const string("Hola mundo!"))));
+//
+//   auto it = m.find(42);
+//   if (it != m.end())
+//     cout << "m[42] points to " << it->second.value() << endl;
+//   cout << "m[33] points to " << m[33].value() << endl;
+//
+// Since copy and assign are not allowed, you can't create a copy of the
+// BoxedValue which means that you can only use a reference to it.
+//
+
+class BoxedValue {
+ public:
+  // Creates an empty BoxedValue. Since the pointer can't be assigned from other
+  // BoxedValues or pointers, this is only useful in places where a default
+  // constructor is required, such as std::map::operator[].
+  BoxedValue() : value_(nullptr), deleter_(nullptr), printer_(nullptr) {}
+
+  // Creates a BoxedValue for the passed pointer |value|. The BoxedValue keeps
+  // the ownership of this pointer and can't be released.
+  template<typename T>
+  explicit BoxedValue(const T* value)
+    : value_(static_cast<const void*>(value)), deleter_(ValueDeleter<T>),
+      printer_(ValuePrinter<T>) {}
+
+  // The move constructor takes ownership of the pointer since the semantics of
+  // it allows to render the passed BoxedValue undefined. You need to use the
+  // move constructor explicitly preventing it from accidental references,
+  // like in:
+  //   BoxedValue new_box(std::move(other_box));
+  BoxedValue(BoxedValue&& other)  // NOLINT(build/c++11)
+      : value_(other.value_), deleter_(other.deleter_),
+        printer_(other.printer_) {
+    other.value_ = nullptr;
+    other.deleter_ = nullptr;
+    other.printer_ = nullptr;
+  }
+
+  // Deletes the |value| passed on construction using the delete for the passed
+  // type.
+  ~BoxedValue() {
+    if (deleter_)
+      deleter_(value_);
+  }
+
+  const void* value() const { return value_; }
+
+  std::string ToString() const {
+    if (!printer_)
+      return "(no printer)";
+    if (!value_)
+      return "(no value)";
+    return printer_(value_);
+  }
+
+  // Static method to call the destructor of the right type.
+  template<typename T>
+  static void ValueDeleter(const void* value) {
+    delete reinterpret_cast<const T*>(value);
+  }
+
+  // Static method to print a type. See boxed_value.cc for common
+  // instantiations.
+  template<typename T>
+  static std::string ValuePrinter(const void* value);
+
+ private:
+  // A pointer to the cached value.
+  const void* value_;
+
+  // A function that calls delete for the right type of value_.
+  void (*deleter_)(const void*);
+
+  // A function that converts value_ to a string.
+  std::string (*printer_)(const void*);
+
+  DISALLOW_COPY_AND_ASSIGN(BoxedValue);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc
new file mode 100644
index 0000000..4914a23
--- /dev/null
+++ b/update_manager/boxed_value_unittest.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2014 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/update_manager/boxed_value.h"
+
+#include <gtest/gtest.h>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/umtest_utils.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::list;
+using std::map;
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// The DeleterMarker flags a bool variable when the class is destroyed.
+class DeleterMarker {
+ public:
+  explicit DeleterMarker(bool* marker) : marker_(marker) { *marker_ = false; }
+
+  ~DeleterMarker() { *marker_ = true; }
+
+ private:
+  friend string BoxedValue::ValuePrinter<DeleterMarker>(const void *);
+
+  // Pointer to the bool marker.
+  bool* marker_;
+};
+
+template<>
+string BoxedValue::ValuePrinter<DeleterMarker>(const void *value) {
+  const DeleterMarker* val = reinterpret_cast<const DeleterMarker*>(value);
+  return base::StringPrintf("DeleterMarker:%s",
+                            *val->marker_ ? "true" : "false");
+}
+
+TEST(UmBoxedValueTest, Deleted) {
+  bool marker = true;
+  const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+  EXPECT_FALSE(marker);
+  BoxedValue* box = new BoxedValue(deleter_marker);
+  EXPECT_FALSE(marker);
+  delete box;
+  EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MoveConstructor) {
+  bool marker = true;
+  const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+  BoxedValue* box = new BoxedValue(deleter_marker);
+  BoxedValue* new_box = new BoxedValue(std::move(*box));
+  // box is now undefined but valid.
+  delete box;
+  EXPECT_FALSE(marker);
+  // The deleter_marker gets deleted at this point.
+  delete new_box;
+  EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedList) {
+  list<BoxedValue> lst;
+  // This is mostly a compile test.
+  lst.emplace_back(new const int{42});
+  lst.emplace_back(new const string("Hello world!"));
+  bool marker;
+  lst.emplace_back(new const DeleterMarker(&marker));
+  EXPECT_FALSE(marker);
+  lst.clear();
+  EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedMap) {
+  map<int, BoxedValue> m;
+  m.emplace(42, BoxedValue(new const string("Hola mundo!")));
+
+  auto it = m.find(42);
+  ASSERT_NE(it, m.end());
+  EXPECT_NE(nullptr, it->second.value());
+  EXPECT_EQ(nullptr, m[33].value());
+}
+
+TEST(UmBoxedValueTest, StringToString) {
+  EXPECT_EQ("Hej Verden!",
+            BoxedValue(new string("Hej Verden!")).ToString());
+}
+
+TEST(UmBoxedValueTest, IntToString) {
+  EXPECT_EQ("42", BoxedValue(new int(42)).ToString());
+}
+
+TEST(UmBoxedValueTest, Int64ToString) {
+  // -123456789012345 doesn't fit in 32-bit integers.
+  EXPECT_EQ("-123456789012345", BoxedValue(
+      new int64_t(-123456789012345LL)).ToString());
+}
+
+TEST(UmBoxedValueTest, UnsignedIntToString) {
+  // 4294967295 is the biggest possible 32-bit unsigned integer.
+  EXPECT_EQ("4294967295",
+            BoxedValue(new unsigned int(4294967295U)).ToString());  // NOLINT
+}
+
+TEST(UmBoxedValueTest, UnsignedInt64ToString) {
+  // 18446744073709551615 is the biggest possible 64-bit unsigned integer.
+  EXPECT_EQ("18446744073709551615", BoxedValue(
+      new uint64_t(18446744073709551615ULL)).ToString());
+}
+
+TEST(UmBoxedValueTest, BoolToString) {
+  EXPECT_EQ("false", BoxedValue(new bool(false)).ToString());
+  EXPECT_EQ("true", BoxedValue(new bool(true)).ToString());
+}
+
+TEST(UmBoxedValueTest, DoubleToString) {
+  EXPECT_EQ("1.501", BoxedValue(new double(1.501)).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeToString) {
+  // Tue Apr 29 22:30:55 UTC 2014 is 1398810655 seconds since the Unix Epoch.
+  EXPECT_EQ("4/29/2014 22:30:55 GMT",
+            BoxedValue(new Time(Time::FromTimeT(1398810655))).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeDeltaToString) {
+  // 12345 seconds is 3 hours, 25 minutes and 45 seconds.
+  EXPECT_EQ("3h25m45s",
+            BoxedValue(new TimeDelta(TimeDelta::FromSeconds(12345)))
+            .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTypeToString) {
+  EXPECT_EQ("Ethernet",
+            BoxedValue(new ConnectionType(ConnectionType::kEthernet))
+            .ToString());
+  EXPECT_EQ("Wifi",
+            BoxedValue(new ConnectionType(ConnectionType::kWifi)).ToString());
+  EXPECT_EQ("Wimax",
+            BoxedValue(new ConnectionType(ConnectionType::kWimax)).ToString());
+  EXPECT_EQ("Bluetooth",
+            BoxedValue(new ConnectionType(ConnectionType::kBluetooth))
+            .ToString());
+  EXPECT_EQ("Cellular",
+            BoxedValue(new ConnectionType(ConnectionType::kCellular))
+            .ToString());
+  EXPECT_EQ("Unknown",
+            BoxedValue(new ConnectionType(ConnectionType::kUnknown))
+            .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTetheringToString) {
+  EXPECT_EQ("Not Detected",
+            BoxedValue(new ConnectionTethering(
+                ConnectionTethering::kNotDetected)).ToString());
+  EXPECT_EQ("Suspected",
+            BoxedValue(new ConnectionTethering(ConnectionTethering::kSuspected))
+            .ToString());
+  EXPECT_EQ("Confirmed",
+            BoxedValue(new ConnectionTethering(ConnectionTethering::kConfirmed))
+            .ToString());
+  EXPECT_EQ("Unknown",
+            BoxedValue(new ConnectionTethering(ConnectionTethering::kUnknown))
+            .ToString());
+}
+
+TEST(UmBoxedValueTest, SetConnectionTypeToString) {
+  set<ConnectionType>* set1 = new set<ConnectionType>;
+  set1->insert(ConnectionType::kWimax);
+  set1->insert(ConnectionType::kEthernet);
+  EXPECT_EQ("Ethernet,Wimax", BoxedValue(set1).ToString());
+
+  set<ConnectionType>* set2 = new set<ConnectionType>;
+  set2->insert(ConnectionType::kWifi);
+  EXPECT_EQ("Wifi", BoxedValue(set2).ToString());
+}
+
+TEST(UmBoxedValueTest, StageToString) {
+  EXPECT_EQ("Idle",
+            BoxedValue(new Stage(Stage::kIdle)).ToString());
+  EXPECT_EQ("Checking For Update",
+            BoxedValue(new Stage(Stage::kCheckingForUpdate)).ToString());
+  EXPECT_EQ("Update Available",
+            BoxedValue(new Stage(Stage::kUpdateAvailable)).ToString());
+  EXPECT_EQ("Downloading",
+            BoxedValue(new Stage(Stage::kDownloading)).ToString());
+  EXPECT_EQ("Verifying",
+            BoxedValue(new Stage(Stage::kVerifying)).ToString());
+  EXPECT_EQ("Finalizing",
+            BoxedValue(new Stage(Stage::kFinalizing)).ToString());
+  EXPECT_EQ("Updated, Need Reboot",
+            BoxedValue(new Stage(Stage::kUpdatedNeedReboot)).ToString());
+  EXPECT_EQ("Reporting Error Event",
+            BoxedValue(new Stage(Stage::kReportingErrorEvent)).ToString());
+  EXPECT_EQ("Attempting Rollback",
+            BoxedValue(new Stage(Stage::kAttemptingRollback)).ToString());
+}
+
+TEST(UmBoxedValueTest, DeleterMarkerToString) {
+  bool marker = false;
+  BoxedValue value = BoxedValue(new DeleterMarker(&marker));
+  EXPECT_EQ("DeleterMarker:false", value.ToString());
+  marker = true;
+  EXPECT_EQ("DeleterMarker:true", value.ToString());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
new file mode 100644
index 0000000..2acccaf
--- /dev/null
+++ b/update_manager/chromeos_policy.cc
@@ -0,0 +1,921 @@
+// Copyright (c) 2014 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/update_manager/chromeos_policy.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/time/time.h>
+
+#include "update_engine/error_code.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/policy_utils.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using std::get;
+using std::max;
+using std::min;
+using std::set;
+using std::string;
+
+namespace {
+
+// Examines |err_code| and decides whether the URL index needs to be advanced,
+// the error count for the URL incremented, or none of the above. In the first
+// case, returns true; in the second case, increments |*url_num_error_p| and
+// returns false; otherwise just returns false.
+//
+// TODO(garnold) Adapted from PayloadState::UpdateFailed() (to be retired).
+bool HandleErrorCode(ErrorCode err_code, int* url_num_error_p) {
+  err_code = chromeos_update_engine::utils::GetBaseErrorCode(err_code);
+  switch (err_code) {
+    // Errors which are good indicators of a problem with a particular URL or
+    // the protocol used in the URL or entities in the communication channel
+    // (e.g. proxies). We should try the next available URL in the next update
+    // check to quickly recover from these errors.
+    case ErrorCode::kPayloadHashMismatchError:
+    case ErrorCode::kPayloadSizeMismatchError:
+    case ErrorCode::kDownloadPayloadVerificationError:
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+    case ErrorCode::kDownloadInvalidMetadataMagicString:
+    case ErrorCode::kDownloadSignatureMissingInManifest:
+    case ErrorCode::kDownloadManifestParseError:
+    case ErrorCode::kDownloadMetadataSignatureError:
+    case ErrorCode::kDownloadMetadataSignatureVerificationError:
+    case ErrorCode::kDownloadMetadataSignatureMismatch:
+    case ErrorCode::kDownloadOperationHashVerificationError:
+    case ErrorCode::kDownloadOperationExecutionError:
+    case ErrorCode::kDownloadOperationHashMismatch:
+    case ErrorCode::kDownloadInvalidMetadataSize:
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+    case ErrorCode::kDownloadOperationHashMissingError:
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+    case ErrorCode::kPayloadMismatchedType:
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+      LOG(INFO) << "Advancing download URL due to error "
+                << chromeos_update_engine::utils::CodeToString(err_code)
+                << " (" << static_cast<int>(err_code) << ")";
+      return true;
+
+    // Errors which seem to be just transient network/communication related
+    // failures and do not indicate any inherent problem with the URL itself.
+    // So, we should keep the current URL but just increment the
+    // failure count to give it more chances. This way, while we maximize our
+    // chances of downloading from the URLs that appear earlier in the response
+    // (because download from a local server URL that appears earlier in a
+    // response is preferable than downloading from the next URL which could be
+    // an Internet URL and thus could be more expensive).
+    case ErrorCode::kError:
+    case ErrorCode::kDownloadTransferError:
+    case ErrorCode::kDownloadWriteError:
+    case ErrorCode::kDownloadStateInitializationError:
+    case ErrorCode::kOmahaErrorInHTTPResponse:  // Aggregate for HTTP errors.
+      LOG(INFO) << "Incrementing URL failure count due to error "
+                << chromeos_update_engine::utils::CodeToString(err_code)
+                << " (" << static_cast<int>(err_code) << ")";
+      *url_num_error_p += 1;
+      return false;
+
+    // Errors which are not specific to a URL and hence shouldn't result in
+    // the URL being penalized. This can happen in two cases:
+    // 1. We haven't started downloading anything: These errors don't cost us
+    // anything in terms of actual payload bytes, so we should just do the
+    // regular retries at the next update check.
+    // 2. We have successfully downloaded the payload: In this case, the
+    // payload attempt number would have been incremented and would take care
+    // of the back-off at the next update check.
+    // In either case, there's no need to update URL index or failure count.
+    case ErrorCode::kOmahaRequestError:
+    case ErrorCode::kOmahaResponseHandlerError:
+    case ErrorCode::kPostinstallRunnerError:
+    case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kInstallDeviceOpenError:
+    case ErrorCode::kKernelDeviceOpenError:
+    case ErrorCode::kDownloadNewPartitionInfoError:
+    case ErrorCode::kNewRootfsVerificationError:
+    case ErrorCode::kNewKernelVerificationError:
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+    case ErrorCode::kOmahaRequestEmptyResponseError:
+    case ErrorCode::kOmahaRequestXMLParseError:
+    case ErrorCode::kOmahaResponseInvalid:
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+    case ErrorCode::kPostinstallPowerwashError:
+    case ErrorCode::kUpdateCanceledByChannelChange:
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kFilesystemVerifierError:
+      LOG(INFO) << "Not changing URL index or failure count due to error "
+                << chromeos_update_engine::utils::CodeToString(err_code)
+                << " (" << static_cast<int>(err_code) << ")";
+      return false;
+
+    case ErrorCode::kSuccess:                            // success code
+    case ErrorCode::kUmaReportedMax:                     // not an error code
+    case ErrorCode::kOmahaRequestHTTPResponseBase:       // aggregated already
+    case ErrorCode::kDevModeFlag:                       // not an error code
+    case ErrorCode::kResumedFlag:                        // not an error code
+    case ErrorCode::kTestImageFlag:                      // not an error code
+    case ErrorCode::kTestOmahaUrlFlag:                   // not an error code
+    case ErrorCode::kSpecialFlags:                       // not an error code
+      // These shouldn't happen. Enumerating these  explicitly here so that we
+      // can let the compiler warn about new error codes that are added to
+      // action_processor.h but not added here.
+      LOG(WARNING) << "Unexpected error "
+                   << chromeos_update_engine::utils::CodeToString(err_code)
+                   << " (" << static_cast<int>(err_code) << ")";
+    // Note: Not adding a default here so as to let the compiler warn us of
+    // any new enums that were added in the .h but not listed in this switch.
+  }
+  return false;
+}
+
+// Checks whether |url| can be used under given download restrictions.
+bool IsUrlUsable(const string& url, bool http_allowed) {
+  return http_allowed || !base::StartsWithASCII(url, "http://", false);
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+const int ChromeOSPolicy::kTimeoutInitialInterval =  7 * 60;
+const int ChromeOSPolicy::kTimeoutPeriodicInterval = 45 * 60;
+const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 4 * 60 * 60;
+const int ChromeOSPolicy::kTimeoutRegularFuzz = 10 * 60;
+const int ChromeOSPolicy::kAttemptBackoffMaxIntervalInDays = 16;
+const int ChromeOSPolicy::kAttemptBackoffFuzzInHours = 12;
+const int ChromeOSPolicy::kMaxP2PAttempts = 10;
+const int ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds = 5 * 24 * 60 * 60;
+
+EvalStatus ChromeOSPolicy::UpdateCheckAllowed(
+    EvaluationContext* ec, State* state, string* error,
+    UpdateCheckParams* result) const {
+  // Set the default return values.
+  result->updates_enabled = true;
+  result->target_channel.clear();
+  result->target_version_prefix.clear();
+  result->is_interactive = false;
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  UpdaterProvider* const updater_provider = state->updater_provider();
+  SystemProvider* const system_provider = state->system_provider();
+
+  // Do not perform any updates if booted from removable device. This decision
+  // is final.
+  const bool* is_boot_device_removable_p = ec->GetValue(
+      system_provider->var_is_boot_device_removable());
+  if (is_boot_device_removable_p && *is_boot_device_removable_p) {
+    LOG(INFO) << "Booted from removable device, disabling update checks.";
+    result->updates_enabled = false;
+    return EvalStatus::kSucceeded;
+  }
+
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    // Check whether updates are disabled by policy.
+    const bool* update_disabled_p = ec->GetValue(
+        dp_provider->var_update_disabled());
+    if (update_disabled_p && *update_disabled_p) {
+      LOG(INFO) << "Updates disabled by policy, blocking update checks.";
+      return EvalStatus::kAskMeAgainLater;
+    }
+
+    // Determine whether a target version prefix is dictated by policy.
+    const string* target_version_prefix_p = ec->GetValue(
+        dp_provider->var_target_version_prefix());
+    if (target_version_prefix_p)
+      result->target_version_prefix = *target_version_prefix_p;
+
+    // Determine whether a target channel is dictated by policy.
+    const bool* release_channel_delegated_p = ec->GetValue(
+        dp_provider->var_release_channel_delegated());
+    if (release_channel_delegated_p && !(*release_channel_delegated_p)) {
+      const string* release_channel_p = ec->GetValue(
+          dp_provider->var_release_channel());
+      if (release_channel_p)
+        result->target_channel = *release_channel_p;
+    }
+  }
+
+  // First, check to see if an interactive update was requested.
+  const UpdateRequestStatus* forced_update_requested_p = ec->GetValue(
+      updater_provider->var_forced_update_requested());
+  if (forced_update_requested_p &&
+      *forced_update_requested_p != UpdateRequestStatus::kNone) {
+    result->is_interactive =
+        (*forced_update_requested_p == UpdateRequestStatus::kInteractive);
+    LOG(INFO) << "Forced update signaled ("
+              << (result->is_interactive ?  "interactive" : "periodic")
+              << "), allowing update check.";
+    return EvalStatus::kSucceeded;
+  }
+
+  // The logic thereafter applies to periodic updates. Bear in mind that we
+  // should not return a final "no" if any of these criteria are not satisfied,
+  // because the system may still update due to an interactive update request.
+
+  // Unofficial builds should not perform periodic update checks.
+  const bool* is_official_build_p = ec->GetValue(
+      system_provider->var_is_official_build());
+  if (is_official_build_p && !(*is_official_build_p)) {
+    LOG(INFO) << "Unofficial build, blocking periodic update checks.";
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+  // If OOBE is enabled, wait until it is completed.
+  const bool* is_oobe_enabled_p = ec->GetValue(
+      state->config_provider()->var_is_oobe_enabled());
+  if (is_oobe_enabled_p && *is_oobe_enabled_p) {
+    const bool* is_oobe_complete_p = ec->GetValue(
+        system_provider->var_is_oobe_complete());
+    if (is_oobe_complete_p && !(*is_oobe_complete_p)) {
+      LOG(INFO) << "OOBE not completed, blocking update checks.";
+      return EvalStatus::kAskMeAgainLater;
+    }
+  }
+
+  // Ensure that periodic update checks are timed properly.
+  Time next_update_check;
+  if (NextUpdateCheckTime(ec, state, error, &next_update_check) !=
+      EvalStatus::kSucceeded) {
+    return EvalStatus::kFailed;
+  }
+  if (!ec->IsWallclockTimeGreaterThan(next_update_check)) {
+    LOG(INFO) << "Periodic check interval not satisfied, blocking until "
+              << chromeos_update_engine::utils::ToString(next_update_check);
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+  // It is time to check for an update.
+  LOG(INFO) << "Allowing update check.";
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateCanStart(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateDownloadParams* result,
+    const UpdateState update_state) const {
+  // Set the default return values. Note that we set persisted values (backoff,
+  // scattering) to the same values presented in the update state. The reason is
+  // that preemptive returns, such as the case where an update check is due,
+  // should not clear off the said values; rather, it is the deliberate
+  // inference of new values that should cause them to be reset.
+  result->update_can_start = false;
+  result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+  result->download_url_idx = -1;
+  result->download_url_allowed = true;
+  result->download_url_num_errors = 0;
+  result->p2p_downloading_allowed = false;
+  result->p2p_sharing_allowed = false;
+  result->do_increment_failures = false;
+  result->backoff_expiry = update_state.backoff_expiry;
+  result->scatter_wait_period = update_state.scatter_wait_period;
+  result->scatter_check_threshold = update_state.scatter_check_threshold;
+
+  // Make sure that we're not due for an update check.
+  UpdateCheckParams check_result;
+  EvalStatus check_status = UpdateCheckAllowed(ec, state, error, &check_result);
+  if (check_status == EvalStatus::kFailed)
+    return EvalStatus::kFailed;
+  bool is_check_due = (check_status == EvalStatus::kSucceeded &&
+                       check_result.updates_enabled == true);
+
+  // Check whether backoff applies, and if not then which URL can be used for
+  // downloading. These require scanning the download error log, and so they are
+  // done together.
+  UpdateBackoffAndDownloadUrlResult backoff_url_result;
+  EvalStatus backoff_url_status = UpdateBackoffAndDownloadUrl(
+      ec, state, error, &backoff_url_result, update_state);
+  if (backoff_url_status == EvalStatus::kFailed)
+    return EvalStatus::kFailed;
+  result->download_url_idx = backoff_url_result.url_idx;
+  result->download_url_num_errors = backoff_url_result.url_num_errors;
+  result->do_increment_failures = backoff_url_result.do_increment_failures;
+  result->backoff_expiry = backoff_url_result.backoff_expiry;
+  bool is_backoff_active =
+      (backoff_url_status == EvalStatus::kAskMeAgainLater) ||
+      !backoff_url_result.backoff_expiry.is_null();
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  bool is_scattering_active = false;
+  EvalStatus scattering_status = EvalStatus::kSucceeded;
+
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    // Check whether scattering applies to this update attempt. We should not be
+    // scattering if this is an interactive update check, or if OOBE is enabled
+    // but not completed.
+    //
+    // Note: current code further suppresses scattering if a "deadline"
+    // attribute is found in the Omaha response. However, it appears that the
+    // presence of this attribute is merely indicative of an OOBE update, during
+    // which we suppress scattering anyway.
+    bool is_scattering_applicable = false;
+    result->scatter_wait_period = kZeroInterval;
+    result->scatter_check_threshold = 0;
+    if (!update_state.is_interactive) {
+      const bool* is_oobe_enabled_p = ec->GetValue(
+          state->config_provider()->var_is_oobe_enabled());
+      if (is_oobe_enabled_p && !(*is_oobe_enabled_p)) {
+        is_scattering_applicable = true;
+      } else {
+        const bool* is_oobe_complete_p = ec->GetValue(
+            state->system_provider()->var_is_oobe_complete());
+        is_scattering_applicable = (is_oobe_complete_p && *is_oobe_complete_p);
+      }
+    }
+
+    // Compute scattering values.
+    if (is_scattering_applicable) {
+      UpdateScatteringResult scatter_result;
+      scattering_status = UpdateScattering(ec, state, error, &scatter_result,
+                                           update_state);
+      if (scattering_status == EvalStatus::kFailed) {
+        return EvalStatus::kFailed;
+      } else {
+        result->scatter_wait_period = scatter_result.wait_period;
+        result->scatter_check_threshold = scatter_result.check_threshold;
+        if (scattering_status == EvalStatus::kAskMeAgainLater ||
+            scatter_result.is_scattering)
+          is_scattering_active = true;
+      }
+    }
+  }
+
+  // Find out whether P2P is globally enabled.
+  bool p2p_enabled;
+  EvalStatus p2p_enabled_status = P2PEnabled(ec, state, error, &p2p_enabled);
+  if (p2p_enabled_status != EvalStatus::kSucceeded)
+    return EvalStatus::kFailed;
+
+  // Is P2P is enabled, consider allowing it for downloading and/or sharing.
+  if (p2p_enabled) {
+    // Sharing via P2P is allowed if not disabled by Omaha.
+    if (update_state.p2p_sharing_disabled) {
+      LOG(INFO) << "Blocked P2P sharing because it is disabled by Omaha.";
+    } else {
+      result->p2p_sharing_allowed = true;
+    }
+
+    // Downloading via P2P is allowed if not disabled by Omaha, an update is not
+    // interactive, and other limits haven't been reached.
+    if (update_state.p2p_downloading_disabled) {
+      LOG(INFO) << "Blocked P2P downloading because it is disabled by Omaha.";
+    } else if (update_state.is_interactive) {
+      LOG(INFO) << "Blocked P2P downloading because update is interactive.";
+    } else if (update_state.p2p_num_attempts >= kMaxP2PAttempts) {
+      LOG(INFO) << "Blocked P2P downloading as it was attempted too many "
+                   "times.";
+    } else if (!update_state.p2p_first_attempted.is_null() &&
+               ec->IsWallclockTimeGreaterThan(
+                   update_state.p2p_first_attempted +
+                   TimeDelta::FromSeconds(kMaxP2PAttemptsPeriodInSeconds))) {
+      LOG(INFO) << "Blocked P2P downloading as its usage timespan exceeds "
+                   "limit.";
+    } else {
+      // P2P download is allowed; if backoff or scattering are active, be sure
+      // to suppress them, yet prevent any download URL from being used.
+      result->p2p_downloading_allowed = true;
+      if (is_backoff_active || is_scattering_active) {
+        is_backoff_active = is_scattering_active = false;
+        result->download_url_allowed = false;
+      }
+    }
+  }
+
+  // Check for various deterrents.
+  if (is_check_due) {
+    result->cannot_start_reason = UpdateCannotStartReason::kCheckDue;
+    return EvalStatus::kSucceeded;
+  }
+  if (is_backoff_active) {
+    result->cannot_start_reason = UpdateCannotStartReason::kBackoff;
+    return backoff_url_status;
+  }
+  if (is_scattering_active) {
+    result->cannot_start_reason = UpdateCannotStartReason::kScattering;
+    return scattering_status;
+  }
+  if (result->download_url_idx < 0 && !result->p2p_downloading_allowed) {
+    result->cannot_start_reason = UpdateCannotStartReason::kCannotDownload;
+    return EvalStatus::kSucceeded;
+  }
+
+  // Update is good to go.
+  result->update_can_start = true;
+  return EvalStatus::kSucceeded;
+}
+
+// TODO(garnold) Logic in this method is based on
+// ConnectionManager::IsUpdateAllowedOver(); be sure to deprecate the latter.
+//
+// TODO(garnold) The current logic generally treats the list of allowed
+// connections coming from the device policy as a whitelist, meaning that it
+// can only be used for enabling connections, but not disable them. Further,
+// certain connection types (like Bluetooth) cannot be enabled even by policy.
+// In effect, the only thing that device policy can change is to enable
+// updates over a cellular network (disabled by default). We may want to
+// revisit this semantics, allowing greater flexibility in defining specific
+// permissions over all types of networks.
+EvalStatus ChromeOSPolicy::UpdateDownloadAllowed(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    bool* result) const {
+  // Get the current connection type.
+  ShillProvider* const shill_provider = state->shill_provider();
+  const ConnectionType* conn_type_p = ec->GetValue(
+      shill_provider->var_conn_type());
+  POLICY_CHECK_VALUE_AND_FAIL(conn_type_p, error);
+  ConnectionType conn_type = *conn_type_p;
+
+  // If we're tethering, treat it as a cellular connection.
+  if (conn_type != ConnectionType::kCellular) {
+    const ConnectionTethering* conn_tethering_p = ec->GetValue(
+        shill_provider->var_conn_tethering());
+    POLICY_CHECK_VALUE_AND_FAIL(conn_tethering_p, error);
+    if (*conn_tethering_p == ConnectionTethering::kConfirmed)
+      conn_type = ConnectionType::kCellular;
+  }
+
+  // By default, we allow updates for all connection types, with exceptions as
+  // noted below. This also determines whether a device policy can override the
+  // default.
+  *result = true;
+  bool device_policy_can_override = false;
+  switch (conn_type) {
+    case ConnectionType::kBluetooth:
+      *result = false;
+      break;
+
+    case ConnectionType::kCellular:
+      *result = false;
+      device_policy_can_override = true;
+      break;
+
+    case ConnectionType::kUnknown:
+      if (error)
+        *error = "Unknown connection type";
+      return EvalStatus::kFailed;
+
+    default:
+      break;  // Nothing to do.
+  }
+
+  // If update is allowed, we're done.
+  if (*result)
+    return EvalStatus::kSucceeded;
+
+  // Check whether the device policy specifically allows this connection.
+  if (device_policy_can_override) {
+    DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+    const bool* device_policy_is_loaded_p = ec->GetValue(
+        dp_provider->var_device_policy_is_loaded());
+    if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+      const set<ConnectionType>* allowed_conn_types_p = ec->GetValue(
+          dp_provider->var_allowed_connection_types_for_update());
+      if (allowed_conn_types_p) {
+        if (allowed_conn_types_p->count(conn_type)) {
+          *result = true;
+          return EvalStatus::kSucceeded;
+        }
+      } else if (conn_type == ConnectionType::kCellular) {
+        // Local user settings can allow updates over cellular iff a policy was
+        // loaded but no allowed connections were specified in it.
+        const bool* update_over_cellular_allowed_p = ec->GetValue(
+            state->updater_provider()->var_cellular_enabled());
+        if (update_over_cellular_allowed_p && *update_over_cellular_allowed_p)
+          *result = true;
+      }
+    }
+  }
+
+  return (*result ? EvalStatus::kSucceeded : EvalStatus::kAskMeAgainLater);
+}
+
+EvalStatus ChromeOSPolicy::P2PEnabled(EvaluationContext* ec,
+                                      State* state,
+                                      std::string* error,
+                                      bool* result) const {
+  bool enabled = false;
+
+  // Determine whether use of P2P is allowed by policy. Even if P2P is not
+  // explicitly allowed, we allow it if the device is enterprise enrolled (that
+  // is, missing or empty owner string).
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+    const bool* policy_au_p2p_enabled_p = ec->GetValue(
+        dp_provider->var_au_p2p_enabled());
+    if (policy_au_p2p_enabled_p) {
+      enabled = *policy_au_p2p_enabled_p;
+    } else {
+      const string* policy_owner_p = ec->GetValue(dp_provider->var_owner());
+      if (!policy_owner_p || policy_owner_p->empty())
+        enabled = true;
+    }
+  }
+
+  // Enable P2P, if so mandated by the updater configuration. This is additive
+  // to whether or not P2P is enabled by device policy.
+  if (!enabled) {
+    const bool* updater_p2p_enabled_p = ec->GetValue(
+        state->updater_provider()->var_p2p_enabled());
+    enabled = updater_p2p_enabled_p && *updater_p2p_enabled_p;
+  }
+
+  *result = enabled;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::P2PEnabledChanged(EvaluationContext* ec,
+                                             State* state,
+                                             std::string* error,
+                                             bool* result,
+                                             bool prev_result) const {
+  EvalStatus status = P2PEnabled(ec, state, error, result);
+  if (status == EvalStatus::kSucceeded && *result == prev_result)
+    return EvalStatus::kAskMeAgainLater;
+  return status;
+}
+
+EvalStatus ChromeOSPolicy::NextUpdateCheckTime(EvaluationContext* ec,
+                                               State* state, string* error,
+                                               Time* next_update_check) const {
+  UpdaterProvider* const updater_provider = state->updater_provider();
+
+  // Don't check for updates too often. We limit the update checks to once every
+  // some interval. The interval is kTimeoutInitialInterval the first time and
+  // kTimeoutPeriodicInterval for the subsequent update checks. If the update
+  // check fails, we increase the interval between the update checks
+  // exponentially until kTimeoutMaxBackoffInterval. Finally, to avoid having
+  // many chromebooks running update checks at the exact same time, we add some
+  // fuzz to the interval.
+  const Time* updater_started_time =
+      ec->GetValue(updater_provider->var_updater_started_time());
+  POLICY_CHECK_VALUE_AND_FAIL(updater_started_time, error);
+
+  const Time* last_checked_time =
+      ec->GetValue(updater_provider->var_last_checked_time());
+
+  const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+  POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+
+  PRNG prng(*seed);
+
+  // If this is the first attempt, compute and return an initial value.
+  if (!last_checked_time || *last_checked_time < *updater_started_time) {
+    *next_update_check = *updater_started_time + FuzzedInterval(
+        &prng, kTimeoutInitialInterval, kTimeoutRegularFuzz);
+    return EvalStatus::kSucceeded;
+  }
+
+  // Check whether the server is enforcing a poll interval; if not, this value
+  // will be zero.
+  const unsigned int* server_dictated_poll_interval = ec->GetValue(
+      updater_provider->var_server_dictated_poll_interval());
+  POLICY_CHECK_VALUE_AND_FAIL(server_dictated_poll_interval, error);
+
+  int interval = *server_dictated_poll_interval;
+  int fuzz = 0;
+
+  // If no poll interval was dictated by server compute a back-off period,
+  // starting from a predetermined base periodic interval and increasing
+  // exponentially by the number of consecutive failed attempts.
+  if (interval == 0) {
+    const unsigned int* consecutive_failed_update_checks = ec->GetValue(
+        updater_provider->var_consecutive_failed_update_checks());
+    POLICY_CHECK_VALUE_AND_FAIL(consecutive_failed_update_checks, error);
+
+    interval = kTimeoutPeriodicInterval;
+    unsigned int num_failures = *consecutive_failed_update_checks;
+    while (interval < kTimeoutMaxBackoffInterval && num_failures) {
+      interval *= 2;
+      num_failures--;
+    }
+  }
+
+  // We cannot back off longer than the predetermined maximum interval.
+  if (interval > kTimeoutMaxBackoffInterval)
+    interval = kTimeoutMaxBackoffInterval;
+
+  // We cannot back off shorter than the predetermined periodic interval. Also,
+  // in this case set the fuzz to a predetermined regular value.
+  if (interval <= kTimeoutPeriodicInterval) {
+    interval = kTimeoutPeriodicInterval;
+    fuzz = kTimeoutRegularFuzz;
+  }
+
+  // If not otherwise determined, defer to a fuzz of +/-(interval / 2).
+  if (fuzz == 0)
+    fuzz = interval;
+
+  *next_update_check = *last_checked_time + FuzzedInterval(
+      &prng, interval, fuzz);
+  return EvalStatus::kSucceeded;
+}
+
+TimeDelta ChromeOSPolicy::FuzzedInterval(PRNG* prng, int interval, int fuzz) {
+  DCHECK_GE(interval, 0);
+  DCHECK_GE(fuzz, 0);
+  int half_fuzz = fuzz / 2;
+  // This guarantees the output interval is non negative.
+  int interval_min = max(interval - half_fuzz, 0);
+  int interval_max = interval + half_fuzz;
+  return TimeDelta::FromSeconds(prng->RandMinMax(interval_min, interval_max));
+}
+
+EvalStatus ChromeOSPolicy::UpdateBackoffAndDownloadUrl(
+    EvaluationContext* ec, State* state, string* error,
+    UpdateBackoffAndDownloadUrlResult* result,
+    const UpdateState& update_state) const {
+  // Sanity checks.
+  DCHECK_GE(update_state.download_errors_max, 0);
+
+  // Set default result values.
+  result->do_increment_failures = false;
+  result->backoff_expiry = update_state.backoff_expiry;
+  result->url_idx = -1;
+  result->url_num_errors = 0;
+
+  const bool* is_official_build_p = ec->GetValue(
+      state->system_provider()->var_is_official_build());
+  bool is_official_build = (is_official_build_p ? *is_official_build_p : true);
+
+  // Check whether backoff is enabled.
+  bool may_backoff = false;
+  if (update_state.is_backoff_disabled) {
+    LOG(INFO) << "Backoff disabled by Omaha.";
+  } else if (update_state.is_interactive) {
+    LOG(INFO) << "No backoff for interactive updates.";
+  } else if (update_state.is_delta_payload) {
+    LOG(INFO) << "No backoff for delta payloads.";
+  } else if (!is_official_build) {
+    LOG(INFO) << "No backoff for unofficial builds.";
+  } else {
+    may_backoff = true;
+  }
+
+  // If previous backoff still in effect, block.
+  if (may_backoff && !update_state.backoff_expiry.is_null() &&
+      !ec->IsWallclockTimeGreaterThan(update_state.backoff_expiry)) {
+    LOG(INFO) << "Previous backoff has not expired, waiting.";
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+  // Determine whether HTTP downloads are forbidden by policy. This only
+  // applies to official system builds; otherwise, HTTP is always enabled.
+  bool http_allowed = true;
+  if (is_official_build) {
+    DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+    const bool* device_policy_is_loaded_p = ec->GetValue(
+        dp_provider->var_device_policy_is_loaded());
+    if (device_policy_is_loaded_p && *device_policy_is_loaded_p) {
+      const bool* policy_http_downloads_enabled_p = ec->GetValue(
+          dp_provider->var_http_downloads_enabled());
+      http_allowed = (!policy_http_downloads_enabled_p ||
+                      *policy_http_downloads_enabled_p);
+    }
+  }
+
+  int url_idx = update_state.last_download_url_idx;
+  if (url_idx < 0)
+    url_idx = -1;
+  bool do_advance_url = false;
+  bool is_failure_occurred = false;
+  Time err_time;
+
+  // Scan the relevant part of the download error log, tracking which URLs are
+  // being used, and accounting the number of errors for each URL. Note that
+  // this process may not traverse all errors provided, as it may decide to bail
+  // out midway depending on the particular errors exhibited, the number of
+  // failures allowed, etc. When this ends, |url_idx| will point to the last URL
+  // used (-1 if starting fresh), |do_advance_url| will determine whether the
+  // URL needs to be advanced, and |err_time| the point in time when the last
+  // reported error occurred.  Additionally, if the error log indicates that an
+  // update attempt has failed (abnormal), then |is_failure_occurred| will be
+  // set to true.
+  const int num_urls = update_state.download_urls.size();
+  int prev_url_idx = -1;
+  int url_num_errors = update_state.last_download_url_num_errors;
+  Time prev_err_time;
+  bool is_first = true;
+  for (const auto& err_tuple : update_state.download_errors) {
+    // Do some sanity checks.
+    int used_url_idx = get<0>(err_tuple);
+    if (is_first && url_idx >= 0 && used_url_idx != url_idx) {
+      LOG(WARNING) << "First URL in error log (" << used_url_idx
+                   << ") not as expected (" << url_idx << ")";
+    }
+    is_first = false;
+    url_idx = used_url_idx;
+    if (url_idx < 0 || url_idx >= num_urls) {
+      LOG(ERROR) << "Download error log contains an invalid URL index ("
+                 << url_idx << ")";
+      return EvalStatus::kFailed;
+    }
+    err_time = get<2>(err_tuple);
+    if (!(prev_err_time.is_null() || err_time >= prev_err_time)) {
+      // TODO(garnold) Monotonicity cannot really be assumed when dealing with
+      // wallclock-based timestamps. However, we're making a simplifying
+      // assumption so as to keep the policy implementation straightforward, for
+      // now. In general, we should convert all timestamp handling in the
+      // UpdateManager to use monotonic time (instead of wallclock), including
+      // the computation of various expiration times (backoff, scattering, etc).
+      // The client will do whatever conversions necessary when
+      // persisting/retrieving these values across reboots. See chromium:408794.
+      LOG(ERROR) << "Download error timestamps not monotonically increasing.";
+      return EvalStatus::kFailed;
+    }
+    prev_err_time = err_time;
+
+    // Ignore errors that happened before the last known failed attempt.
+    if (!update_state.failures_last_updated.is_null() &&
+        err_time <= update_state.failures_last_updated)
+      continue;
+
+    if (prev_url_idx >= 0) {
+      if (url_idx < prev_url_idx) {
+        LOG(ERROR) << "The URLs in the download error log have wrapped around ("
+                   << prev_url_idx << "->" << url_idx
+                   << "). This should not have happened and means that there's "
+                      "a bug. To be conservative, we record a failed attempt "
+                      "(invalidating the rest of the error log) and resume "
+                      "download from the first usable URL.";
+        url_idx = -1;
+        is_failure_occurred = true;
+        break;
+      }
+
+      if (url_idx > prev_url_idx) {
+        url_num_errors = 0;
+        do_advance_url = false;
+      }
+    }
+
+    if (HandleErrorCode(get<1>(err_tuple), &url_num_errors) ||
+        url_num_errors > update_state.download_errors_max)
+      do_advance_url = true;
+
+    prev_url_idx = url_idx;
+  }
+
+  // If required, advance to the next usable URL. If the URLs wraparound, we
+  // mark an update attempt failure. Also be sure to set the download error
+  // count to zero.
+  if (url_idx < 0 || do_advance_url) {
+    url_num_errors = 0;
+    int start_url_idx = -1;
+    do {
+      if (++url_idx == num_urls) {
+        url_idx = 0;
+        // We only mark failure if an actual advancing of a URL was required.
+        if (do_advance_url)
+          is_failure_occurred = true;
+      }
+
+      if (start_url_idx < 0)
+        start_url_idx = url_idx;
+      else if (url_idx == start_url_idx)
+        url_idx = -1;  // No usable URL.
+    } while (url_idx >= 0 &&
+             !IsUrlUsable(update_state.download_urls[url_idx], http_allowed));
+  }
+
+  // If we have a download URL but a failure was observed, compute a new backoff
+  // expiry (if allowed). The backoff period is generally 2 ^ (num_failures - 1)
+  // days, bounded by the size of int and kAttemptBackoffMaxIntervalInDays, and
+  // fuzzed by kAttemptBackoffFuzzInHours hours. Backoff expiry is computed from
+  // the latest recorded time of error.
+  Time backoff_expiry;
+  if (url_idx >= 0 && is_failure_occurred && may_backoff) {
+    CHECK(!err_time.is_null())
+        << "We must have an error timestamp if a failure occurred!";
+    const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+    POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+    PRNG prng(*seed);
+    int exp = min(update_state.num_failures,
+                       static_cast<int>(sizeof(int)) * 8 - 2);
+    TimeDelta backoff_interval = TimeDelta::FromDays(
+        min(1 << exp, kAttemptBackoffMaxIntervalInDays));
+    TimeDelta backoff_fuzz = TimeDelta::FromHours(kAttemptBackoffFuzzInHours);
+    TimeDelta wait_period = FuzzedInterval(&prng, backoff_interval.InSeconds(),
+                                           backoff_fuzz.InSeconds());
+    backoff_expiry = err_time + wait_period;
+
+    // If the newly computed backoff already expired, nullify it.
+    if (ec->IsWallclockTimeGreaterThan(backoff_expiry))
+      backoff_expiry = Time();
+  }
+
+  result->do_increment_failures = is_failure_occurred;
+  result->backoff_expiry = backoff_expiry;
+  result->url_idx = url_idx;
+  result->url_num_errors = url_num_errors;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateScattering(
+    EvaluationContext* ec,
+    State* state,
+    string* error,
+    UpdateScatteringResult* result,
+    const UpdateState& update_state) const {
+  // Preconditions. These stem from the postconditions and usage contract.
+  DCHECK(update_state.scatter_wait_period >= kZeroInterval);
+  DCHECK_GE(update_state.scatter_check_threshold, 0);
+
+  // Set default result values.
+  result->is_scattering = false;
+  result->wait_period = kZeroInterval;
+  result->check_threshold = 0;
+
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+  // Ensure that a device policy is loaded.
+  const bool* device_policy_is_loaded_p = ec->GetValue(
+      dp_provider->var_device_policy_is_loaded());
+  if (!(device_policy_is_loaded_p && *device_policy_is_loaded_p))
+    return EvalStatus::kSucceeded;
+
+  // Is scattering enabled by policy?
+  const TimeDelta* scatter_factor_p = ec->GetValue(
+      dp_provider->var_scatter_factor());
+  if (!scatter_factor_p || *scatter_factor_p == kZeroInterval)
+    return EvalStatus::kSucceeded;
+
+  // Obtain a pseudo-random number generator.
+  const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+  POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+  PRNG prng(*seed);
+
+  // Step 1: Maintain the scattering wait period.
+  //
+  // If no wait period was previously determined, or it no longer fits in the
+  // scatter factor, then generate a new one. Otherwise, keep the one we have.
+  TimeDelta wait_period = update_state.scatter_wait_period;
+  if (wait_period == kZeroInterval || wait_period > *scatter_factor_p) {
+    wait_period = TimeDelta::FromSeconds(
+        prng.RandMinMax(1, scatter_factor_p->InSeconds()));
+  }
+
+  // If we surpassed the wait period or the max scatter period associated with
+  // the update, then no wait is needed.
+  Time wait_expires = (update_state.first_seen +
+                       min(wait_period, update_state.scatter_wait_period_max));
+  if (ec->IsWallclockTimeGreaterThan(wait_expires))
+    wait_period = kZeroInterval;
+
+  // Step 2: Maintain the update check threshold count.
+  //
+  // If an update check threshold is not specified then generate a new
+  // one.
+  int check_threshold = update_state.scatter_check_threshold;
+  if (check_threshold == 0) {
+    check_threshold = prng.RandMinMax(
+        update_state.scatter_check_threshold_min,
+        update_state.scatter_check_threshold_max);
+  }
+
+  // If the update check threshold is not within allowed range then nullify it.
+  // TODO(garnold) This is compliant with current logic found in
+  // OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied(). We may want
+  // to change it so that it behaves similarly to the wait period case, namely
+  // if the current value exceeds the maximum, we set a new one within range.
+  if (check_threshold > update_state.scatter_check_threshold_max)
+    check_threshold = 0;
+
+  // If the update check threshold is non-zero and satisfied, then nullify it.
+  if (check_threshold > 0 && update_state.num_checks >= check_threshold)
+    check_threshold = 0;
+
+  bool is_scattering = (wait_period != kZeroInterval || check_threshold);
+  EvalStatus ret = EvalStatus::kSucceeded;
+  if (is_scattering && wait_period == update_state.scatter_wait_period &&
+      check_threshold == update_state.scatter_check_threshold)
+    ret = EvalStatus::kAskMeAgainLater;
+  result->is_scattering = is_scattering;
+  result->wait_period = wait_period;
+  result->check_threshold = check_threshold;
+  return ret;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
new file mode 100644
index 0000000..e400cdc
--- /dev/null
+++ b/update_manager/chromeos_policy.h
@@ -0,0 +1,191 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+
+#include <string>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/prng.h"
+
+namespace chromeos_update_manager {
+
+// Output information from UpdateBackoffAndDownloadUrl.
+struct UpdateBackoffAndDownloadUrlResult {
+  // Whether the failed attempt count (maintained by the caller) needs to be
+  // incremented.
+  bool do_increment_failures;
+  // The current backoff expiry. Null if backoff is not in effect.
+  base::Time backoff_expiry;
+  // The new URL index to use and number of download errors associated with it.
+  // Significant iff |do_increment_failures| is false and |backoff_expiry| is
+  // null. Negative value means no usable URL was found.
+  int url_idx;
+  int url_num_errors;
+};
+
+// Parameters for update scattering, as returned by UpdateScattering.
+struct UpdateScatteringResult {
+  bool is_scattering;
+  base::TimeDelta wait_period;
+  int check_threshold;
+};
+
+// ChromeOSPolicy implements the policy-related logic used in ChromeOS.
+class ChromeOSPolicy : public Policy {
+ public:
+  ChromeOSPolicy() {}
+  ~ChromeOSPolicy() override {}
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateCheckParams* result) const override;
+
+  EvalStatus UpdateCanStart(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      UpdateDownloadParams* result,
+      UpdateState update_state) const override;
+
+  EvalStatus UpdateDownloadAllowed(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabled(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabledChanged(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result,
+      bool prev_result) const override;
+
+ protected:
+  // Policy override.
+  std::string PolicyName() const override { return "ChromeOSPolicy"; }
+
+ private:
+  friend class UmChromeOSPolicyTest;
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              FirstCheckIsAtMostInitialIntervalAfterStart);
+  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz);
+  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz);
+  FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval);
+  FRIEND_TEST(UmChromeOSPolicyTest, ExponentialBackoffIsCapped);
+  FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout);
+  FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringNewCountThresholdApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies);
+  FRIEND_TEST(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartAllowedInteractivePreventsScattering);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod);
+
+  // Auxiliary constant (zero by default).
+  const base::TimeDelta kZeroInterval;
+
+  // Default update check timeout interval/fuzz values used to compute the
+  // NextUpdateCheckTime(), in seconds. Actual fuzz is within +/- half of the
+  // indicated value.
+  static const int kTimeoutInitialInterval;
+  static const int kTimeoutPeriodicInterval;
+  static const int kTimeoutMaxBackoffInterval;
+  static const int kTimeoutRegularFuzz;
+
+  // Maximum update attempt backoff interval and fuzz.
+  static const int kAttemptBackoffMaxIntervalInDays;
+  static const int kAttemptBackoffFuzzInHours;
+
+  // Maximum number of times we'll allow using P2P for the same update payload.
+  static const int kMaxP2PAttempts;
+  // Maximum period of time allowed for download a payload via P2P, in seconds.
+  static const int kMaxP2PAttemptsPeriodInSeconds;
+
+  // A private policy implementation returning the wallclock timestamp when
+  // the next update check should happen.
+  // TODO(garnold) We should probably change that to infer a monotonic
+  // timestamp, which will make the update check intervals more resilient to
+  // clock skews. Might require switching some of the variables exported by the
+  // UpdaterProvider to report monotonic time, as well.
+  EvalStatus NextUpdateCheckTime(EvaluationContext* ec, State* state,
+                                 std::string* error,
+                                 base::Time* next_update_check) const;
+
+  // Returns a TimeDelta based on the provided |interval| seconds +/- half
+  // |fuzz| seconds. The return value is guaranteed to be a non-negative
+  // TimeDelta.
+  static base::TimeDelta FuzzedInterval(PRNG* prng, int interval, int fuzz);
+
+  // A private policy for determining backoff and the download URL to use.
+  // Within |update_state|, |backoff_expiry| and |is_backoff_disabled| are used
+  // for determining whether backoff is still in effect; if not,
+  // |download_errors| is scanned past |failures_last_updated|, and a new
+  // download URL from |download_urls| is found and written to |result->url_idx|
+  // (-1 means no usable URL exists); |download_errors_max| determines the
+  // maximum number of attempts per URL, according to the Omaha response. If an
+  // update failure is identified then |result->do_increment_failures| is set to
+  // true; if backoff is enabled, a new backoff period is computed (from the
+  // time of failure) based on |num_failures|. Otherwise, backoff expiry is
+  // nullified, indicating that no backoff is in effect.
+  //
+  // If backing off but the previous backoff expiry is unchanged, returns
+  // |EvalStatus::kAskMeAgainLater|. Otherwise:
+  //
+  // * If backing off with a new expiry time, then |result->backoff_expiry| is
+  //   set to this time.
+  //
+  // * Else, |result->backoff_expiry| is set to null, indicating that no backoff
+  //   is in effect.
+  //
+  // In any of these cases, returns |EvalStatus::kSucceeded|. If an error
+  // occurred, returns |EvalStatus::kFailed|.
+  EvalStatus UpdateBackoffAndDownloadUrl(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateBackoffAndDownloadUrlResult* result,
+      const UpdateState& update_state) const;
+
+  // A private policy for checking whether scattering is due. Writes in |result|
+  // the decision as to whether or not to scatter; a wallclock-based scatter
+  // wait period, which ranges from zero (do not wait) and no greater than the
+  // current scatter factor provided by the device policy (if available) or the
+  // maximum wait period determined by Omaha; and an update check-based
+  // threshold between zero (no threshold) and the maximum number determined by
+  // the update engine. Within |update_state|, |scatter_wait_period| should
+  // contain the last scattering period returned by this function, or zero if no
+  // wait period is known; |scatter_check_threshold| is the last update check
+  // threshold, or zero if no such threshold is known. If not scattering, or if
+  // any of the scattering values has changed, returns |EvalStatus::kSucceeded|;
+  // otherwise, |EvalStatus::kAskMeAgainLater|.
+  EvalStatus UpdateScattering(EvaluationContext* ec, State* state,
+                              std::string* error,
+                              UpdateScatteringResult* result,
+                              const UpdateState& update_state) const;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeOSPolicy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
new file mode 100644
index 0000000..2c935fd
--- /dev/null
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -0,0 +1,1608 @@
+// Copyright (c) 2014 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/update_manager/chromeos_policy.h"
+
+#include <set>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <base/time/time.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::set;
+using std::string;
+using std::tuple;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+class UmChromeOSPolicyTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    SetUpDefaultClock();
+    eval_ctx_ = new EvaluationContext(&fake_clock_, TimeDelta::FromSeconds(5));
+    SetUpDefaultState();
+    SetUpDefaultDevicePolicy();
+  }
+
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  // Sets the clock to fixed values.
+  void SetUpDefaultClock() {
+    fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L));
+    fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
+  }
+
+  void SetUpDefaultState() {
+    fake_state_.updater_provider()->var_updater_started_time()->reset(
+        new Time(fake_clock_.GetWallclockTime()));
+    fake_state_.updater_provider()->var_last_checked_time()->reset(
+        new Time(fake_clock_.GetWallclockTime()));
+    fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+        reset(new unsigned int{0});
+    fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+        reset(new unsigned int{0});
+    fake_state_.updater_provider()->var_forced_update_requested()->
+        reset(new UpdateRequestStatus{UpdateRequestStatus::kNone});
+
+    fake_state_.random_provider()->var_seed()->reset(
+        new uint64_t(4));  // chosen by fair dice roll.
+                           // guaranteed to be random.
+
+    // No device policy loaded by default.
+    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+        new bool(false));
+
+    // OOBE is enabled by default.
+    fake_state_.config_provider()->var_is_oobe_enabled()->reset(
+        new bool(true));
+
+    // For the purpose of the tests, this is an official build and OOBE was
+    // completed.
+    fake_state_.system_provider()->var_is_official_build()->reset(
+        new bool(true));
+    fake_state_.system_provider()->var_is_oobe_complete()->reset(
+        new bool(true));
+    fake_state_.system_provider()->var_is_boot_device_removable()->reset(
+        new bool(false));
+
+    // Connection is wifi, untethered.
+    fake_state_.shill_provider()->var_conn_type()->
+        reset(new ConnectionType(ConnectionType::kWifi));
+    fake_state_.shill_provider()->var_conn_tethering()->
+        reset(new ConnectionTethering(ConnectionTethering::kNotDetected));
+  }
+
+  // Sets up a default device policy that does not impose any restrictions
+  // (HTTP) nor enables any features (P2P).
+  void SetUpDefaultDevicePolicy() {
+    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+        new bool(true));
+    fake_state_.device_policy_provider()->var_update_disabled()->reset(
+        new bool(false));
+    fake_state_.device_policy_provider()->
+        var_allowed_connection_types_for_update()->reset(nullptr);
+    fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+        new TimeDelta());
+    fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+        new bool(true));
+    fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+        new bool(false));
+    fake_state_.device_policy_provider()->var_release_channel_delegated()->
+        reset(new bool(true));
+  }
+
+  // Configures the UpdateCheckAllowed policy to return a desired value by
+  // faking the current wall clock time as needed. Restores the default state.
+  // This is used when testing policies that depend on this one.
+  void SetUpdateCheckAllowed(bool allow_check) {
+    Time next_update_check;
+    ExpectPolicyStatus(EvalStatus::kSucceeded,
+                       &ChromeOSPolicy::NextUpdateCheckTime,
+                       &next_update_check);
+    SetUpDefaultState();
+    SetUpDefaultDevicePolicy();
+    Time curr_time = next_update_check;
+    if (allow_check)
+      curr_time += TimeDelta::FromSeconds(1);
+    else
+      curr_time -= TimeDelta::FromSeconds(1);
+    fake_clock_.SetWallclockTime(curr_time);
+  }
+
+  // Returns a default UpdateState structure:
+  UpdateState GetDefaultUpdateState(TimeDelta first_seen_period) {
+    Time first_seen_time = fake_clock_.GetWallclockTime() - first_seen_period;
+    UpdateState update_state = UpdateState();
+
+    // This is a non-interactive check returning a delta payload, seen for the
+    // first time (|first_seen_period| ago). Clearly, there were no failed
+    // attempts so far.
+    update_state.is_interactive = false;
+    update_state.is_delta_payload = false;
+    update_state.first_seen = first_seen_time;
+    update_state.num_checks = 1;
+    update_state.num_failures = 0;
+    update_state.failures_last_updated = Time();  // Needs to be zero.
+    // There's a single HTTP download URL with a maximum of 10 retries.
+    update_state.download_urls = vector<string>{"http://fake/url/"};
+    update_state.download_errors_max = 10;
+    // Download was never attempted.
+    update_state.last_download_url_idx = -1;
+    update_state.last_download_url_num_errors = 0;
+    // There were no download errors.
+    update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+    // P2P is not disabled by Omaha.
+    update_state.p2p_downloading_disabled = false;
+    update_state.p2p_sharing_disabled = false;
+    // P2P was not attempted.
+    update_state.p2p_num_attempts = 0;
+    update_state.p2p_first_attempted = Time();
+    // No active backoff period, backoff is not disabled by Omaha.
+    update_state.backoff_expiry = Time();
+    update_state.is_backoff_disabled = false;
+    // There is no active scattering wait period (max 7 days allowed) nor check
+    // threshold (none allowed).
+    update_state.scatter_wait_period = TimeDelta();
+    update_state.scatter_check_threshold = 0;
+    update_state.scatter_wait_period_max = TimeDelta::FromDays(7);
+    update_state.scatter_check_threshold_min = 0;
+    update_state.scatter_check_threshold_max = 0;
+
+    return update_state;
+  }
+
+  // Runs the passed |policy_method| policy and expects it to return the
+  // |expected| return value.
+  template<typename T, typename R, typename... Args>
+  void ExpectPolicyStatus(
+      EvalStatus expected,
+      T policy_method,
+      R* result, Args... args) {
+    string error = "<None>";
+    eval_ctx_->ResetEvaluation();
+    EXPECT_EQ(expected,
+              (policy_.*policy_method)(eval_ctx_.get(), &fake_state_, &error,
+                                       result, args...))
+        << "Returned error: " << error
+        << "\nEvaluation context: " << eval_ctx_->DumpContext();
+  }
+
+  chromeos::FakeMessageLoop loop_{nullptr};
+  FakeClock fake_clock_;
+  FakeState fake_state_;
+  scoped_refptr<EvaluationContext> eval_ctx_;
+  ChromeOSPolicy policy_;  // ChromeOSPolicy under test.
+};
+
+TEST_F(UmChromeOSPolicyTest, FirstCheckIsAtMostInitialIntervalAfterStart) {
+  Time next_update_check;
+
+  // Set the last update time so it'll appear as if this is a first update check
+  // in the lifetime of the current updater.
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(fake_clock_.GetWallclockTime() - TimeDelta::FromMinutes(10)));
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(fake_clock_.GetWallclockTime(), next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutInitialInterval +
+          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz) {
+  // Ensure that we're using the correct interval (kPeriodicInterval) and fuzz
+  // (kTimeoutRegularFuzz) as base values for period updates.
+  Time next_update_check;
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutPeriodicInterval -
+          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutPeriodicInterval +
+          ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz) {
+  // Ensure that we're properly backing off and fuzzing in the presence of
+  // failed updates attempts.
+  Time next_update_check;
+
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+      reset(new unsigned int{2});
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  int expected_interval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          expected_interval - expected_interval / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          expected_interval + expected_interval / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval) {
+  // Policy honors the server provided check poll interval.
+  Time next_update_check;
+
+  const unsigned int kInterval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+  fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+      reset(new unsigned int{kInterval});
+  // We should not be backing off in this case.
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+      reset(new unsigned int{2});
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          kInterval - kInterval / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          kInterval + kInterval / 2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, ExponentialBackoffIsCapped) {
+  Time next_update_check;
+
+  fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+      reset(new unsigned int{100});
+
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  EXPECT_LE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval -
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval / 2),
+      next_update_check);
+  EXPECT_GE(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval +
+          ChromeOSPolicy::kTimeoutMaxBackoffInterval /2),
+      next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) {
+  // We get the next update_check timestamp from the policy's private method
+  // and then we check the public method respects that value on the normal
+  // case.
+  Time next_update_check;
+  Time last_checked_time =
+      fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
+
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  UpdateCheckParams result;
+
+  // Check that the policy blocks until the next_update_check is reached.
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check - TimeDelta::FromSeconds(1));
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE) {
+  // Update checks are deferred until OOBE is completed.
+
+  // Ensure that update is not allowed even if wait period is satisfied.
+  Time next_update_check;
+  Time last_checked_time =
+      fake_clock_.GetWallclockTime() + TimeDelta::FromMinutes(1234);
+
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+  fake_state_.system_provider()->var_is_oobe_complete()->reset(
+      new bool(false));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+
+  // Now check that it is allowed if OOBE is completed.
+  SetUpDefaultClock();
+  SetUpDefaultState();
+  fake_state_.updater_provider()->var_last_checked_time()->reset(
+      new Time(last_checked_time));
+  fake_clock_.SetWallclockTime(next_update_check + TimeDelta::FromSeconds(1));
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWithAttributes) {
+  // Update check is allowed, response includes attributes for use in the
+  // request.
+  SetUpdateCheckAllowed(true);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_target_version_prefix()->
+      reset(new string("1.2"));
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->
+      reset(new bool(false));
+  fake_state_.device_policy_provider()->var_release_channel()->
+      reset(new string("foo-channel"));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_EQ("1.2", result.target_version_prefix);
+  EXPECT_EQ("foo-channel", result.target_channel);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedUpdatesDisabledForUnofficialBuilds) {
+  // UpdateCheckAllowed should return kAskMeAgainLater if this is an unofficial
+  // build; we don't want periodic update checks on developer images.
+
+  fake_state_.system_provider()->var_is_official_build()->reset(
+      new bool(false));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedUpdatesDisabledForRemovableBootDevice) {
+  // UpdateCheckAllowed should return false (kSucceeded) if the image booted
+  // from a removable device.
+
+  fake_state_.system_provider()->var_is_boot_device_removable()->reset(
+      new bool(true));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_FALSE(result.updates_enabled);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedUpdatesDisabledByPolicy) {
+  // UpdateCheckAllowed should return kAskMeAgainLater because a device policy
+  // is loaded and prohibits updates.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_update_disabled()->reset(
+      new bool(true));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCheckAllowedForcedUpdateRequestedInteractive) {
+  // UpdateCheckAllowed should return true because a forced update request was
+  // signaled for an interactive update.
+
+  SetUpdateCheckAllowed(true);
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus(UpdateRequestStatus::kInteractive));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_TRUE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedForcedUpdateRequestedPeriodic) {
+  // UpdateCheckAllowed should return true because a forced update request was
+  // signaled for a periodic check.
+
+  SetUpdateCheckAllowed(true);
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus(UpdateRequestStatus::kPeriodic));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.updates_enabled);
+  EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsCheckAllowedError) {
+  // The UpdateCanStart policy fails, not being able to query
+  // UpdateCheckAllowed.
+
+  // Configure the UpdateCheckAllowed policy to fail.
+  fake_state_.updater_provider()->var_updater_started_time()->reset(nullptr);
+
+  // Check that the UpdateCanStart fails.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kFailed,
+                     &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedCheckDue) {
+  // The UpdateCanStart policy returns false because we are due for another
+  // update check. Ensure that download related values are still returned.
+
+  SetUpdateCheckAllowed(true);
+
+  // Check that the UpdateCanStart returns false.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kCheckDue, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_EQ(0, result.download_url_num_errors);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoDevicePolicy) {
+  // The UpdateCanStart policy returns true; no device policy is loaded.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns true with no further attributes.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_FALSE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBlankPolicy) {
+  // The UpdateCanStart policy returns true; device policy is loaded but imposes
+  // no restrictions on updating.
+
+  SetUpdateCheckAllowed(false);
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateCanStart, &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_FALSE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedBackoffNewWaitPeriodApplies) {
+  // The UpdateCanStart policy returns false; failures are reported and a new
+  // backoff period is enacted.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedBackoffPrevWaitPeriodStillApplies) {
+  // The UpdateCanStart policy returns false; a previously enacted backoff
+  // period still applies.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.failures_last_updated = curr_time;
+  update_state.backoff_expiry = curr_time + TimeDelta::FromMinutes(3);
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+  EXPECT_FALSE(result.do_increment_failures);
+  EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffSatisfied) {
+  // The UpdateCanStart policy returns true; a previously enacted backoff period
+  // has elapsed, we're good to go.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.failures_last_updated = curr_time - TimeDelta::FromSeconds(1);
+  update_state.backoff_expiry = curr_time - TimeDelta::FromSeconds(1);
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffDisabled) {
+  // The UpdateCanStart policy returns false; failures are reported but backoff
+  // is disabled.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.is_backoff_disabled = true;
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffInteractive) {
+  // The UpdateCanStart policy returns false; failures are reported but this is
+  // an interactive update check.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.is_interactive = true;
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffDelta) {
+  // The UpdateCanStart policy returns false; failures are reported but this is
+  // a delta payload.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  update_state.is_delta_payload = true;
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffUnofficialBuild) {
+  // The UpdateCanStart policy returns false; failures are reported but this is
+  // an unofficial build.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+
+  fake_state_.system_provider()->var_is_official_build()->
+      reset(new bool(false));
+
+  // Check that UpdateCanStart returns false and a new backoff expiry is
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsScatteringFailed) {
+  // The UpdateCanStart policy fails because the UpdateScattering policy it
+  // depends on fails (unset variable).
+
+  SetUpdateCheckAllowed(false);
+
+  // Override the default seed variable with a null value so that the policy
+  // request would fail.
+  // TODO(garnold) This failure may or may not fail a number
+  // sub-policies/decisions, like scattering and backoff. We'll need a more
+  // deliberate setup to ensure that we're failing what we want to be failing.
+  fake_state_.random_provider()->var_seed()->reset(nullptr);
+
+  // Check that the UpdateCanStart fails.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kFailed,
+                     &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering applies due to an unsatisfied wait period, which was newly
+  // generated.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+
+  // Check that the UpdateCanStart returns false and a new wait period
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_LT(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies) {
+  // The UpdateCanStart policy returns false w/ kAskMeAgainLater; device policy
+  // is loaded and a previously generated scattering period still applies, none
+  // of the scattering values has changed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+  // Check that the UpdateCanStart returns false and a new wait period
+  // generated.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringNewCountThresholdApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering applies due to an unsatisfied update check count threshold.
+  //
+  // This ensures a non-zero check threshold, which may or may not be combined
+  // with a non-zero wait period (for which we cannot reliably control).
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns false.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_LE(2, result.scatter_check_threshold);
+  EXPECT_GE(5, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies) {
+  // The UpdateCanStart policy returns false; device policy is loaded and
+  // scattering due to a previously generated count threshold still applies.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_check_threshold = 3;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns false.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+  EXPECT_EQ(3, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering is enabled, but both wait period and check threshold are
+  // satisfied.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(120)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(75));
+  update_state.num_checks = 4;
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(60);
+  update_state.scatter_check_threshold = 3;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedInteractivePreventsScattering) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering would have applied, except that the update check is interactive
+  // and so it is suppressed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.is_interactive = true;
+  update_state.scatter_check_threshold = 0;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedOobePreventsScattering) {
+  // The UpdateCanStart policy returns true; device policy is loaded and
+  // scattering would have applied, except that OOBE was not completed and so it
+  // is suppressed.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromSeconds(1)));
+  fake_state_.system_provider()->var_is_oobe_complete()->reset(new bool(false));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.is_interactive = true;
+  update_state.scatter_check_threshold = 0;
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 5;
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithAttributes) {
+  // The UpdateCanStart policy returns true; device policy permits both HTTP and
+  // P2P updates, as well as a non-empty target channel string.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithP2PFromUpdater) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but the updater is configured to allow P2P and overrules the
+  // setting.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PDownloadingBlockedDueToOmaha) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP, but
+  // policy blocks P2P downloading because Omaha forbids it.  P2P sharing is
+  // still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_downloading_disabled = true;
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PSharingBlockedDueToOmaha) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP, but
+  // policy blocks P2P sharing because Omaha forbids it.  P2P downloading is
+  // still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_sharing_disabled = true;
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_FALSE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP but
+  // blocks P2P download, because the max number of P2P downloads have been
+  // attempted. P2P sharing is still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_num_attempts = ChromeOSPolicy::kMaxP2PAttempts;
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod) {
+  // The UpdateCanStart policy returns true; device policy permits HTTP but
+  // blocks P2P download, because the max period for attempt to download via P2P
+  // has elapsed. P2P sharing is still permitted.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.p2p_num_attempts = 1;
+  update_state.p2p_first_attempted =
+      fake_clock_.GetWallclockTime() -
+      TimeDelta::FromSeconds(
+          ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds + 1);
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_FALSE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedWithHttpUrlForUnofficialBuild) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but marking this an unofficial build overrules the HTTP
+  // setting.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+  fake_state_.system_provider()->var_is_official_build()->
+      reset(new bool(false));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithHttpsUrl) {
+  // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+  // P2P updates, but an HTTPS URL is provided and selected for download.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Add an HTTPS URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.download_urls.emplace_back("https://secure/url/");
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(1, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedMaxErrorsNotExceeded) {
+  // The UpdateCanStart policy returns true; the first URL has download errors
+  // but does not exceed the maximum allowed number of failures, so it is stilli
+  // usable.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed enough times to
+  // disqualify the current (first) URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 5;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12);
+  for (int i = 0; i < 5; i++) {
+    update_state.download_errors.emplace_back(
+        0, ErrorCode::kDownloadTransferError, t);
+    t += TimeDelta::FromSeconds(1);
+  }
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(5, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlMaxExceeded) {
+  // The UpdateCanStart policy returns true; the first URL exceeded the maximum
+  // allowed number of failures, but a second URL is available.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed enough times to
+  // disqualify the current (first) URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 10;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  Time t = fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(12);
+  for (int i = 0; i < 11; i++) {
+    update_state.download_errors.emplace_back(
+        0, ErrorCode::kDownloadTransferError, t);
+    t += TimeDelta::FromSeconds(1);
+  }
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(1, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlHardError) {
+  // The UpdateCanStart policy returns true; the first URL fails with a hard
+  // error, but a second URL is available.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed in a way that
+  // causes it to switch directly to the next URL.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 10;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kPayloadHashMismatchError,
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(1, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedUrlWrapsAround) {
+  // The UpdateCanStart policy returns true; URL search properly wraps around
+  // the last one on the list.
+
+  SetUpdateCheckAllowed(false);
+
+  // Add a second URL; update with this URL attempted and failed in a way that
+  // causes it to switch directly to the next URL. We must disable backoff in
+  // order for it not to interfere.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  update_state.num_checks = 1;
+  update_state.is_backoff_disabled = true;
+  update_state.download_urls.emplace_back("http://another/fake/url/");
+  update_state.download_errors.emplace_back(
+      1, ErrorCode::kPayloadHashMismatchError,
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedNoUsableUrls) {
+  // The UpdateCanStart policy returns false; there's a single HTTP URL but its
+  // use is forbidden by policy.
+  //
+  // Note: In the case where no usable URLs are found, the policy should not
+  // increment the number of failed attempts! Doing so would result in a
+  // non-idempotent semantics, and does not fall within the intended purpose of
+  // the backoff mechanism anyway.
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns false.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_FALSE(result.update_can_start);
+  EXPECT_EQ(UpdateCannotStartReason::kCannotDownload,
+            result.cannot_start_reason);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoUsableUrlsButP2PEnabled) {
+  // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+  // use is forbidden by policy, however P2P is enabled. The result indicates
+  // that no URL can be used.
+  //
+  // Note: The number of failed attempts should not increase in this case (see
+  // above test).
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_GT(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedNoUsableUrlsButEnterpriseEnrolled) {
+  // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+  // use is forbidden by policy, and P2P is unset on the policy, however the
+  // device is enterprise-enrolled so P2P is allowed. The result indicates that
+  // no URL can be used.
+  //
+  // Note: The number of failed attempts should not increase in this case (see
+  // above test).
+
+  SetUpdateCheckAllowed(false);
+
+  // Override specific device policy attributes.
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr);
+  fake_state_.device_policy_provider()->var_owner()->reset(nullptr);
+  fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+      new bool(false));
+
+  // Check that the UpdateCanStart returns true.
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_GT(0, result.download_url_idx);
+  EXPECT_TRUE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedEthernetDefault) {
+  // Ethernet is always allowed.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kEthernet));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWifiDefault) {
+  // Wifi is allowed if not tethered.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCurrentConnectionNotAllowedWifiTetheredDefault) {
+  // Tethered wifi is not allowed by default.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+  fake_state_.shill_provider()->var_conn_tethering()->
+      reset(new ConnectionTethering(ConnectionTethering::kConfirmed));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateDownloadAllowedWifiTetheredPolicyOverride) {
+  // Tethered wifi can be allowed by policy.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+  fake_state_.shill_provider()->var_conn_tethering()->
+      reset(new ConnectionTethering(ConnectionTethering::kConfirmed));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kCellular);
+  fake_state_.device_policy_provider()->
+      var_allowed_connection_types_for_update()->
+      reset(new set<ConnectionType>(allowed_connections));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateDownloadAllowedWimaxDefault) {
+  // Wimax is always allowed.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kWifi));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCurrentConnectionNotAllowedBluetoothDefault) {
+  // Bluetooth is never allowed.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kBluetooth));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCurrentConnectionNotAllowedBluetoothPolicyCannotOverride) {
+  // Bluetooth cannot be allowed even by policy.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kBluetooth));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kBluetooth);
+  fake_state_.device_policy_provider()->
+      var_allowed_connection_types_for_update()->
+      reset(new set<ConnectionType>(allowed_connections));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCurrentConnectionNotAllowedCellularDefault) {
+  // Cellular is not allowed by default.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kCellular));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+                     &Policy::UpdateDownloadAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateDownloadAllowedCellularPolicyOverride) {
+  // Update over cellular can be enabled by policy.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kCellular));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kCellular);
+  fake_state_.device_policy_provider()->
+      var_allowed_connection_types_for_update()->
+      reset(new set<ConnectionType>(allowed_connections));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateDownloadAllowedCellularUserOverride) {
+  // Update over cellular can be enabled by user settings, but only if policy
+  // is present and does not determine allowed connections.
+
+  fake_state_.shill_provider()->var_conn_type()->
+      reset(new ConnectionType(ConnectionType::kCellular));
+  set<ConnectionType> allowed_connections;
+  allowed_connections.insert(ConnectionType::kCellular);
+  fake_state_.updater_provider()->var_cellular_enabled()->
+      reset(new bool(true));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded,
+                     &Policy::UpdateDownloadAllowed, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedScatteringSupressedDueToP2P) {
+  // The UpdateCanStart policy returns true; scattering should have applied, but
+  // P2P download is allowed. Scattering values are nonetheless returned, and so
+  // are download URL values, albeit the latter are not allowed to be used.
+
+  SetUpdateCheckAllowed(false);
+  fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+      new TimeDelta(TimeDelta::FromMinutes(2)));
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+                     &result, update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_FALSE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_FALSE(result.do_increment_failures);
+  EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+  EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanStartAllowedBackoffSupressedDueToP2P) {
+  // The UpdateCanStart policy returns true; backoff should have applied, but
+  // P2P download is allowed. Backoff values are nonetheless returned, and so
+  // are download URL values, albeit the latter are not allowed to be used.
+
+  SetUpdateCheckAllowed(false);
+
+  const Time curr_time = fake_clock_.GetWallclockTime();
+  UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(10));
+  update_state.download_errors_max = 1;
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(8));
+  update_state.download_errors.emplace_back(
+      0, ErrorCode::kDownloadTransferError,
+      curr_time - TimeDelta::FromSeconds(2));
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  UpdateDownloadParams result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+                     update_state);
+  EXPECT_TRUE(result.update_can_start);
+  EXPECT_EQ(0, result.download_url_idx);
+  EXPECT_FALSE(result.download_url_allowed);
+  EXPECT_EQ(0, result.download_url_num_errors);
+  EXPECT_TRUE(result.p2p_downloading_allowed);
+  EXPECT_TRUE(result.p2p_sharing_allowed);
+  EXPECT_TRUE(result.do_increment_failures);
+  EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledNotAllowed) {
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_FALSE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByDevicePolicy) {
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+      new bool(true));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByUpdater) {
+  fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedDeviceEnterpriseEnrolled) {
+  fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr);
+  fake_state_.device_policy_provider()->var_owner()->reset(nullptr);
+
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+  EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledChangedBlocks) {
+  bool result;
+  ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::P2PEnabledChanged,
+                     &result, false);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/config_provider.h b/update_manager/config_provider.h
new file mode 100644
index 0000000..1f6904a
--- /dev/null
+++ b/update_manager/config_provider.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for const system configurations. This provider reads the
+// configuration from a file on /etc.
+class ConfigProvider : public Provider {
+ public:
+  // Returns a variable stating whether the out of the box experience (OOBE) is
+  // enabled on this device. A value of false means that the device doesn't have
+  // an OOBE workflow.
+  virtual Variable<bool>* var_is_oobe_enabled() = 0;
+
+ protected:
+  ConfigProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConfigProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
diff --git a/update_manager/default_policy.cc b/update_manager/default_policy.cc
new file mode 100644
index 0000000..d49591a
--- /dev/null
+++ b/update_manager/default_policy.cc
@@ -0,0 +1,95 @@
+// Copyright (c) 2014 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/update_manager/default_policy.h"
+
+namespace {
+
+// A fixed minimum interval between consecutive allowed update checks. This
+// needs to be long enough to prevent busywork and/or DDoS attacks on Omaha, but
+// at the same time short enough to allow the machine to update itself
+// reasonably soon.
+const int kCheckIntervalInSeconds = 15 * 60;
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+DefaultPolicy::DefaultPolicy(chromeos_update_engine::ClockInterface* clock)
+    : clock_(clock), aux_state_(new DefaultPolicyState()) {}
+
+EvalStatus DefaultPolicy::UpdateCheckAllowed(
+    EvaluationContext* ec, State* state, std::string* error,
+    UpdateCheckParams* result) const {
+  result->updates_enabled = true;
+  result->target_channel.clear();
+  result->target_version_prefix.clear();
+  result->is_interactive = false;
+
+  // Ensure that the minimum interval is set. If there's no clock, this defaults
+  // to always allowing the update.
+  if (!aux_state_->IsLastCheckAllowedTimeSet() ||
+      ec->IsMonotonicTimeGreaterThan(
+          aux_state_->last_check_allowed_time() +
+          base::TimeDelta::FromSeconds(kCheckIntervalInSeconds))) {
+    if (clock_)
+      aux_state_->set_last_check_allowed_time(clock_->GetMonotonicTime());
+    return EvalStatus::kSucceeded;
+  }
+
+  return EvalStatus::kAskMeAgainLater;
+}
+
+EvalStatus DefaultPolicy::UpdateCanStart(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    UpdateDownloadParams* result,
+    const UpdateState update_state) const {
+  result->update_can_start = true;
+  result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+  result->download_url_idx = 0;
+  result->download_url_allowed = true;
+  result->download_url_num_errors = 0;
+  result->p2p_downloading_allowed = false;
+  result->p2p_sharing_allowed = false;
+  result->do_increment_failures = false;
+  result->backoff_expiry = base::Time();
+  result->scatter_wait_period = base::TimeDelta();
+  result->scatter_check_threshold = 0;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::UpdateDownloadAllowed(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    bool* result) const {
+  *result = true;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::P2PEnabled(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    bool* result) const {
+  *result = false;
+  return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::P2PEnabledChanged(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    bool* result,
+    bool prev_result) const {
+  // This policy will always prohibit P2P, so this is signaling to the caller
+  // that the decision is final (because the current value is the same as the
+  // previous one) and there's no need to issue another call.
+  *result = false;
+  return EvalStatus::kSucceeded;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/default_policy.h b/update_manager/default_policy.h
new file mode 100644
index 0000000..294afea
--- /dev/null
+++ b/update_manager/default_policy.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_DEFAULT_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// Auxiliary state class for DefaultPolicy evaluations.
+//
+// IMPORTANT: The use of a state object in policies is generally forbidden, as
+// it was a design decision to keep policy calls side-effect free. We make an
+// exception here to ensure that DefaultPolicy indeed serves as a safe (and
+// secure) fallback option. This practice should be avoided when imlpementing
+// other policies.
+class DefaultPolicyState {
+ public:
+  DefaultPolicyState() {}
+
+  bool IsLastCheckAllowedTimeSet() const {
+    return last_check_allowed_time_ != base::Time::Max();
+  }
+
+  // Sets/returns the point time on the monotonic time scale when the latest
+  // check allowed was recorded.
+  void set_last_check_allowed_time(base::Time timestamp) {
+    last_check_allowed_time_ = timestamp;
+  }
+  base::Time last_check_allowed_time() const {
+    return last_check_allowed_time_;
+  }
+
+ private:
+  base::Time last_check_allowed_time_ = base::Time::Max();
+};
+
+// The DefaultPolicy is a safe Policy implementation that doesn't fail. The
+// values returned by this policy are safe default in case of failure of the
+// actual policy being used by the UpdateManager.
+class DefaultPolicy : public Policy {
+ public:
+  explicit DefaultPolicy(chromeos_update_engine::ClockInterface* clock);
+  DefaultPolicy() : DefaultPolicy(nullptr) {}
+  ~DefaultPolicy() override {}
+
+  // Policy overrides.
+  EvalStatus UpdateCheckAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateCheckParams* result) const override;
+
+  EvalStatus UpdateCanStart(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateDownloadParams* result,
+      UpdateState update_state) const override;
+
+  EvalStatus UpdateDownloadAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabled(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result) const override;
+
+  EvalStatus P2PEnabledChanged(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result, bool prev_result) const override;
+
+ protected:
+  // Policy override.
+  std::string PolicyName() const override { return "DefaultPolicy"; }
+
+ private:
+  // A clock interface.
+  chromeos_update_engine::ClockInterface* clock_;
+
+  // An auxiliary state object.
+  std::unique_ptr<DefaultPolicyState> aux_state_;
+
+  DISALLOW_COPY_AND_ASSIGN(DefaultPolicy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
diff --git a/update_manager/device_policy_provider.h b/update_manager/device_policy_provider.h
new file mode 100644
index 0000000..1259610
--- /dev/null
+++ b/update_manager/device_policy_provider.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include <base/time/time.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provides access to the current DevicePolicy.
+class DevicePolicyProvider : public Provider {
+ public:
+  ~DevicePolicyProvider() override {}
+
+  // Variable stating whether the DevicePolicy was loaded.
+  virtual Variable<bool>* var_device_policy_is_loaded() = 0;
+
+  // Variables mapping the information received on the DevicePolicy protobuf.
+  virtual Variable<std::string>* var_release_channel() = 0;
+
+  virtual Variable<bool>* var_release_channel_delegated() = 0;
+
+  virtual Variable<bool>* var_update_disabled() = 0;
+
+  virtual Variable<std::string>* var_target_version_prefix() = 0;
+
+  // Returns a non-negative scatter interval used for updates.
+  virtual Variable<base::TimeDelta>* var_scatter_factor() = 0;
+
+  // Variable returning the set of connection types allowed for updates. The
+  // identifiers returned are consistent with the ones returned by the
+  // ShillProvider.
+  virtual Variable<std::set<ConnectionType>>*
+      var_allowed_connection_types_for_update() = 0;
+
+  // Variable stating the name of the device owner. For enterprise enrolled
+  // devices, this will be an empty string.
+  virtual Variable<std::string>* var_owner() = 0;
+
+  virtual Variable<bool>* var_http_downloads_enabled() = 0;
+
+  virtual Variable<bool>* var_au_p2p_enabled() = 0;
+
+ protected:
+  DevicePolicyProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DevicePolicyProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/evaluation_context-inl.h b/update_manager/evaluation_context-inl.h
new file mode 100644
index 0000000..9dfed38
--- /dev/null
+++ b/update_manager/evaluation_context-inl.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+
+#include <string>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+template<typename T>
+const T* EvaluationContext::GetValue(Variable<T>* var) {
+  if (var == nullptr) {
+    LOG(ERROR) << "GetValue received an uninitialized variable.";
+    return nullptr;
+  }
+
+  // Search for the value on the cache first.
+  ValueCacheMap::iterator it = value_cache_.find(var);
+  if (it != value_cache_.end())
+    return reinterpret_cast<const T*>(it->second.value());
+
+  // Get the value from the variable if not found on the cache.
+  std::string errmsg;
+  const T* result = var->GetValue(RemainingTime(evaluation_monotonic_deadline_),
+                                  &errmsg);
+  if (result == nullptr) {
+    LOG(WARNING) << "Error reading Variable " << var->GetName() << ": \""
+        << errmsg << "\"";
+  }
+  // Cache the value for the next time. The map of CachedValues keeps the
+  // ownership of the pointer until the map is destroyed.
+  value_cache_.emplace(
+    static_cast<BaseVariable*>(var), BoxedValue(result));
+  return result;
+}
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
diff --git a/update_manager/evaluation_context.cc b/update_manager/evaluation_context.cc
new file mode 100644
index 0000000..66275de
--- /dev/null
+++ b/update_manager/evaluation_context.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2014 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/update_manager/evaluation_context.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/json/json_writer.h>
+#include <base/location.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+
+#include "update_engine/utils.h"
+
+using base::Callback;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+using std::unique_ptr;
+
+namespace {
+
+// Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
+// |ref_time| is sooner than the current value of |*reeval_time|, in which case
+// the latter is updated to the former.
+bool IsTimeGreaterThanHelper(Time ref_time, Time curr_time,
+                             Time* reeval_time) {
+  if (curr_time > ref_time)
+    return true;
+  // Remember the nearest reference we've checked against in this evaluation.
+  if (*reeval_time > ref_time)
+    *reeval_time = ref_time;
+  return false;
+}
+
+// If |expires| never happens (maximal value), returns the maximal interval;
+// otherwise, returns the difference between |expires| and |curr|.
+TimeDelta GetTimeout(Time curr, Time expires) {
+  if (expires.is_max())
+    return TimeDelta::Max();
+  return expires - curr;
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+EvaluationContext::EvaluationContext(
+    ClockInterface* clock,
+    TimeDelta evaluation_timeout,
+    TimeDelta expiration_timeout,
+    unique_ptr<Callback<void(EvaluationContext*)>> unregister_cb)
+    : clock_(clock),
+      evaluation_timeout_(evaluation_timeout),
+      expiration_timeout_(expiration_timeout),
+      unregister_cb_(std::move(unregister_cb)),
+      weak_ptr_factory_(this) {
+  ResetEvaluation();
+  ResetExpiration();
+}
+
+EvaluationContext::~EvaluationContext() {
+  RemoveObserversAndTimeout();
+  if (unregister_cb_.get())
+    unregister_cb_->Run(this);
+}
+
+unique_ptr<Closure> EvaluationContext::RemoveObserversAndTimeout() {
+  for (auto& it : value_cache_) {
+    if (it.first->GetMode() == kVariableModeAsync)
+      it.first->RemoveObserver(this);
+  }
+  MessageLoop::current()->CancelTask(timeout_event_);
+  timeout_event_ = MessageLoop::kTaskIdNull;
+
+  return unique_ptr<Closure>(callback_.release());
+}
+
+TimeDelta EvaluationContext::RemainingTime(Time monotonic_deadline) const {
+  if (monotonic_deadline.is_max())
+    return TimeDelta::Max();
+  TimeDelta remaining = monotonic_deadline - clock_->GetMonotonicTime();
+  return std::max(remaining, TimeDelta());
+}
+
+Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
+  return (timeout.is_max() ? Time::Max() :
+          clock_->GetMonotonicTime() + timeout);
+}
+
+void EvaluationContext::ValueChanged(BaseVariable* var) {
+  DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
+  OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnTimeout() {
+  DLOG(INFO) << "OnTimeout() called due to "
+             << (timeout_marks_expiration_ ? "expiration" : "poll interval");
+  timeout_event_ = MessageLoop::kTaskIdNull;
+  is_expired_ = timeout_marks_expiration_;
+  OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnValueChangedOrTimeout() {
+  // Copy the callback handle locally, allowing it to be reassigned.
+  unique_ptr<Closure> callback = RemoveObserversAndTimeout();
+
+  if (callback.get())
+    callback->Run();
+}
+
+bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) {
+  return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_,
+                                 &reevaluation_time_wallclock_);
+}
+
+bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) {
+  return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_,
+                                 &reevaluation_time_monotonic_);
+}
+
+void EvaluationContext::ResetEvaluation() {
+  evaluation_start_wallclock_ = clock_->GetWallclockTime();
+  evaluation_start_monotonic_ = clock_->GetMonotonicTime();
+  reevaluation_time_wallclock_ = Time::Max();
+  reevaluation_time_monotonic_ = Time::Max();
+  evaluation_monotonic_deadline_ = MonotonicDeadline(evaluation_timeout_);
+
+  // Remove the cached values of non-const variables
+  for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
+    if (it->first->GetMode() == kVariableModeConst) {
+      ++it;
+    } else {
+      it = value_cache_.erase(it);
+    }
+  }
+}
+
+void EvaluationContext::ResetExpiration() {
+  expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_);
+  is_expired_ = false;
+}
+
+bool EvaluationContext::RunOnValueChangeOrTimeout(Closure callback) {
+  // Check that the method was not called more than once.
+  if (callback_.get()) {
+    LOG(ERROR) << "RunOnValueChangeOrTimeout called more than once.";
+    return false;
+  }
+
+  // Check that the context did not yet expire.
+  if (is_expired()) {
+    LOG(ERROR) << "RunOnValueChangeOrTimeout called on an expired context.";
+    return false;
+  }
+
+  // Handle reevaluation due to a Is{Wallclock,Monotonic}TimeGreaterThan(). We
+  // choose the smaller of the differences between evaluation start time and
+  // reevaluation time among the wallclock and monotonic scales.
+  TimeDelta timeout = std::min(
+      GetTimeout(evaluation_start_wallclock_, reevaluation_time_wallclock_),
+      GetTimeout(evaluation_start_monotonic_, reevaluation_time_monotonic_));
+
+  // Handle reevaluation due to async or poll variables.
+  bool waiting_for_value_change = false;
+  for (auto& it : value_cache_) {
+    switch (it.first->GetMode()) {
+      case kVariableModeAsync:
+        DLOG(INFO) << "Waiting for value on " << it.first->GetName();
+        it.first->AddObserver(this);
+        waiting_for_value_change = true;
+        break;
+      case kVariableModePoll:
+        timeout = std::min(timeout, it.first->GetPollInterval());
+        break;
+      case kVariableModeConst:
+        // Ignored.
+        break;
+    }
+  }
+
+  // Check if the re-evaluation is actually being scheduled. If there are no
+  // events waited for, this function should return false.
+  if (!waiting_for_value_change && timeout.is_max())
+    return false;
+
+  // Ensure that we take into account the expiration timeout.
+  TimeDelta expiration = RemainingTime(expiration_monotonic_deadline_);
+  timeout_marks_expiration_ = expiration < timeout;
+  if (timeout_marks_expiration_)
+    timeout = expiration;
+
+  // Store the reevaluation callback.
+  callback_.reset(new Closure(callback));
+
+  // Schedule a timeout event, if one is set.
+  if (!timeout.is_max()) {
+    DLOG(INFO) << "Waiting for timeout in "
+               << chromeos_update_engine::utils::FormatTimeDelta(timeout);
+    timeout_event_ = MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&EvaluationContext::OnTimeout,
+                   weak_ptr_factory_.GetWeakPtr()),
+        timeout);
+  }
+
+  return true;
+}
+
+string EvaluationContext::DumpContext() const {
+  base::DictionaryValue* variables = new base::DictionaryValue();
+  for (auto& it : value_cache_) {
+    variables->SetString(it.first->GetName(), it.second.ToString());
+  }
+
+  base::DictionaryValue value;
+  value.Set("variables", variables);  // Adopts |variables|.
+  value.SetString(
+      "evaluation_start_wallclock",
+      chromeos_update_engine::utils::ToString(evaluation_start_wallclock_));
+  value.SetString(
+      "evaluation_start_monotonic",
+      chromeos_update_engine::utils::ToString(evaluation_start_monotonic_));
+
+  string json_str;
+  base::JSONWriter::WriteWithOptions(
+      value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_str);
+  base::TrimWhitespaceASCII(json_str, base::TRIM_TRAILING, &json_str);
+
+  return json_str;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/evaluation_context.h b/update_manager/evaluation_context.h
new file mode 100644
index 0000000..3d259f3
--- /dev/null
+++ b/update_manager/evaluation_context.h
@@ -0,0 +1,209 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/boxed_value.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// The EvaluationContext class is the interface between a policy implementation
+// and the state. The EvaluationContext tracks the variables used by a policy
+// request and caches the returned values, owning those cached values.
+// The same EvaluationContext should be re-used for all the evaluations of the
+// same policy request (an AsyncPolicyRequest might involve several
+// re-evaluations). Each evaluation of the EvaluationContext is run at a given
+// point in time, which is used as a reference for the evaluation timeout and
+// the time based queries of the policy, such as
+// Is{Wallclock,Monotonic}TimeGreaterThan().
+//
+// Example:
+//
+//   scoped_refptr<EvaluationContext> ec = new EvaluationContext(...);
+//
+//   ...
+//   // The following call to ResetEvaluation() is optional. Use it to reset the
+//   // evaluation time if the EvaluationContext isn't used right after its
+//   // construction.
+//   ec->ResetEvaluation();
+//   EvalStatus status = policy->SomeMethod(ec, state, &result, args...);
+//
+//   ...
+//   // Run a closure when any of the used async variables changes its value or
+//   // the timeout for re-query the values happens again.
+//   ec->RunOnValueChangeOrTimeout(closure);
+//   // If the provided |closure| wants to re-evaluate the policy, it should
+//   // call ec->ResetEvaluation() to start a new evaluation.
+//
+class EvaluationContext : public base::RefCounted<EvaluationContext>,
+                          private BaseVariable::ObserverInterface {
+ public:
+  EvaluationContext(
+      chromeos_update_engine::ClockInterface* clock,
+      base::TimeDelta evaluation_timeout,
+      base::TimeDelta expiration_timeout,
+      std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb);
+  EvaluationContext(chromeos_update_engine::ClockInterface* clock,
+                    base::TimeDelta evaluation_timeout)
+      : EvaluationContext(
+          clock, evaluation_timeout, base::TimeDelta::Max(),
+          std::unique_ptr<base::Callback<void(EvaluationContext*)>>()) {}
+  ~EvaluationContext();
+
+  // Returns a pointer to the value returned by the passed variable |var|. The
+  // EvaluationContext instance keeps the ownership of the returned object. The
+  // returned object is valid during the life of the evaluation, even if the
+  // passed Variable changes it.
+  //
+  // In case of error, a null value is returned.
+  template<typename T>
+  const T* GetValue(Variable<T>* var);
+
+  // Returns whether the evaluation time has surpassed |timestamp|, on either
+  // the ClockInterface::GetWallclockTime() or
+  // ClockInterface::GetMonotonicTime() scales, respectively.
+  bool IsWallclockTimeGreaterThan(base::Time timestamp);
+  bool IsMonotonicTimeGreaterThan(base::Time timestamp);
+
+  // Returns whether the evaluation context has expired.
+  bool is_expired() const { return is_expired_; }
+
+  // TODO(deymo): Move the following methods to an interface only visible by the
+  // UpdateManager class and not the policy implementations.
+
+  // Resets the EvaluationContext to its initial state removing all the
+  // non-const cached variables and re-setting the evaluation time. This should
+  // be called right before any new evaluation starts.
+  void ResetEvaluation();
+
+  // Clears the expiration status of the EvaluationContext and resets its
+  // expiration timeout based on |expiration_timeout_|. This should be called if
+  // expiration occurred, prior to re-evaluating the policy.
+  void ResetExpiration();
+
+  // Schedules the passed |callback| closure to be called when a cached
+  // variable changes its value, a polling interval passes, or the context
+  // expiration occurs. If none of these events can happen, for example if
+  // there's no cached variable, this method returns false.
+  //
+  // Right before the passed closure is called the EvaluationContext is
+  // reseted, removing all the non-const cached values.
+  bool RunOnValueChangeOrTimeout(base::Closure callback);
+
+  // Returns a textual representation of the evaluation context,
+  // including the variables and their values. This is intended only
+  // to help with debugging and the format may change in the future.
+  std::string DumpContext() const;
+
+  // Removes all the Observers callbacks and timeout events scheduled by
+  // RunOnValueChangeOrTimeout(). Also releases and returns the closure
+  // associated with these events. This method is idempotent.
+  std::unique_ptr<base::Closure> RemoveObserversAndTimeout();
+
+ private:
+  friend class UmEvaluationContextTest;
+
+  // BaseVariable::ObserverInterface override.
+  void ValueChanged(BaseVariable* var) override;
+
+  // Called from the main loop when a scheduled timeout has passed.
+  void OnTimeout();
+
+  // Removes the observers from the used Variables and cancels the timeout,
+  // then executes the scheduled callback.
+  void OnValueChangedOrTimeout();
+
+  // If |monotonic_deadline| is not Time::Max(), returns the remaining time
+  // until it is reached, or zero if it has passed. Otherwise, returns
+  // TimeDelta::Max().
+  base::TimeDelta RemainingTime(base::Time monotonic_deadline) const;
+
+  // Returns a monotonic clock timestamp at which |timeout| will have elapsed
+  // since the current time.
+  base::Time MonotonicDeadline(base::TimeDelta timeout);
+
+  // A map to hold the cached values for every variable.
+  typedef std::map<BaseVariable*, BoxedValue> ValueCacheMap;
+
+  // The cached values of the called Variables.
+  ValueCacheMap value_cache_;
+
+  // A callback used for triggering re-evaluation upon a value change or poll
+  // timeout, or notifying about the evaluation context expiration. It is up to
+  // the caller to determine whether or not expiration occurred via
+  // is_expired().
+  std::unique_ptr<base::Closure> callback_;
+
+  // The TaskId returned by the message loop identifying the timeout callback.
+  // Used for canceling the timeout callback.
+  chromeos::MessageLoop::TaskId timeout_event_ =
+      chromeos::MessageLoop::kTaskIdNull;
+
+  // Whether a timeout event firing marks the expiration of the evaluation
+  // context.
+  bool timeout_marks_expiration_;
+
+  // Whether the evaluation context has indeed expired.
+  bool is_expired_ = false;
+
+  // Pointer to the mockable clock interface;
+  chromeos_update_engine::ClockInterface* const clock_;
+
+  // The timestamps when the evaluation of this EvaluationContext started,
+  // corresponding to ClockInterface::GetWallclockTime() and
+  // ClockInterface::GetMonotonicTime(), respectively. These values are reset
+  // every time ResetEvaluation() is called.
+  base::Time evaluation_start_wallclock_;
+  base::Time evaluation_start_monotonic_;
+
+  // The timestamps when a reevaluation should be triggered due to various
+  // expected value changes, corresponding to ClockInterface::GetWallclockTime()
+  // and ClockInterface::GetMonotonicTIme(), respectively. These timestamps are
+  // greater or equal to corresponding |evaluation_start_{wallclock,monotonic}_|
+  // counterparts since they are in the future; however, they may be smaller
+  // than the current corresponding times during the course of evaluation.
+  base::Time reevaluation_time_wallclock_;
+  base::Time reevaluation_time_monotonic_;
+
+  // The timeout of an evaluation.
+  const base::TimeDelta evaluation_timeout_;
+
+  // The timestamp in the ClockInterface::GetMonotonicTime() scale at which the
+  // current evaluation should finish.
+  base::Time evaluation_monotonic_deadline_;
+
+  // The expiration timeout of the evaluation context.
+  const base::TimeDelta expiration_timeout_;
+
+  // The monotonic clock deadline at which expiration occurs.
+  base::Time expiration_monotonic_deadline_;
+
+  // A callback for unregistering the context upon destruction.
+  std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb_;
+
+  base::WeakPtrFactory<EvaluationContext> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(EvaluationContext);
+};
+
+}  // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/evaluation_context-inl.h"
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
diff --git a/update_manager/evaluation_context_unittest.cc b/update_manager/evaluation_context_unittest.cc
new file mode 100644
index 0000000..2b59026
--- /dev/null
+++ b/update_manager/evaluation_context_unittest.cc
@@ -0,0 +1,477 @@
+// Copyright (c) 2014 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/update_manager/evaluation_context.h"
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/mock_variable.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using chromeos::MessageLoopRunUntil;
+using chromeos_update_engine::FakeClock;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+void DoNothing() {}
+
+// Sets the value of the passed pointer to true.
+void SetTrue(bool* value) {
+  *value = true;
+}
+
+bool GetBoolean(bool* value) {
+  return *value;
+}
+
+template<typename T>
+void ReadVar(scoped_refptr<EvaluationContext> ec, Variable<T>* var) {
+  ec->GetValue(var);
+}
+
+// Runs |evaluation|; if the value pointed by |count_p| is greater than zero,
+// decrement it and schedule a reevaluation; otherwise, writes true to |done_p|.
+void EvaluateRepeatedly(Closure evaluation, scoped_refptr<EvaluationContext> ec,
+                        int* count_p, bool* done_p) {
+  evaluation.Run();
+
+  // Schedule reevaluation if needed.
+  if (*count_p > 0) {
+    Closure closure = Bind(EvaluateRepeatedly, evaluation, ec, count_p, done_p);
+    ASSERT_TRUE(ec->RunOnValueChangeOrTimeout(closure))
+        << "Failed to schedule reevaluation, count_p=" << *count_p;
+    (*count_p)--;
+  } else {
+    *done_p = true;
+  }
+}
+
+}  // namespace
+
+class UmEvaluationContextTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    // Apr 22, 2009 19:25:00 UTC (this is a random reference point).
+    fake_clock_.SetMonotonicTime(Time::FromTimeT(1240428300));
+    // Mar 2, 2006 1:23:45 UTC.
+    fake_clock_.SetWallclockTime(Time::FromTimeT(1141262625));
+    eval_ctx_ = new EvaluationContext(
+        &fake_clock_, default_timeout_, default_timeout_,
+        unique_ptr<base::Callback<void(EvaluationContext*)>>(nullptr));
+  }
+
+  void TearDown() override {
+    // Ensure that the evaluation context did not leak and is actually being
+    // destroyed.
+    if (eval_ctx_) {
+      base::WeakPtr<EvaluationContext> eval_ctx_weak_alias =
+          eval_ctx_->weak_ptr_factory_.GetWeakPtr();
+      ASSERT_NE(nullptr, eval_ctx_weak_alias.get());
+      eval_ctx_ = nullptr;
+      EXPECT_EQ(nullptr, eval_ctx_weak_alias.get())
+          << "The evaluation context was not destroyed! This is likely a bug "
+             "in how the test was written, look for leaking handles to the EC, "
+             "possibly through closure objects.";
+    }
+
+    // Check that the evaluation context removed all the observers.
+    EXPECT_TRUE(fake_int_var_.observer_list_.empty());
+    EXPECT_TRUE(fake_async_var_.observer_list_.empty());
+    EXPECT_TRUE(fake_const_var_.observer_list_.empty());
+    EXPECT_TRUE(fake_poll_var_.observer_list_.empty());
+
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  TimeDelta default_timeout_ = TimeDelta::FromSeconds(5);
+
+  chromeos::FakeMessageLoop loop_{nullptr};
+  FakeClock fake_clock_;
+  scoped_refptr<EvaluationContext> eval_ctx_;
+
+  // FakeVariables used for testing the EvaluationContext. These are required
+  // here to prevent them from going away *before* the EvaluationContext under
+  // test does, which keeps a reference to them.
+  FakeVariable<bool> fail_var_ = {"fail_var", kVariableModePoll};
+  FakeVariable<int> fake_int_var_ = {"fake_int", kVariableModePoll};
+  FakeVariable<string> fake_async_var_ = {"fake_async", kVariableModeAsync};
+  FakeVariable<string> fake_const_var_ = {"fake_const", kVariableModeConst};
+  FakeVariable<string> fake_poll_var_ = {"fake_poll",
+                                         TimeDelta::FromSeconds(1)};
+  StrictMock<MockVariable<string>> mock_var_async_ {
+    "mock_var_async", kVariableModeAsync};
+  StrictMock<MockVariable<string>> mock_var_poll_ {
+    "mock_var_poll", kVariableModePoll};
+};
+
+TEST_F(UmEvaluationContextTest, GetValueFails) {
+  // FakeVariable is initialized as returning null.
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(&fake_int_var_));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueFailsWithInvalidVar) {
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(static_cast<Variable<int>*>(nullptr)));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueReturns) {
+  const int* p_fake_int;
+
+  fake_int_var_.reset(new int(42));
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  ASSERT_NE(nullptr, p_fake_int);
+  EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCached) {
+  const int* p_fake_int;
+
+  fake_int_var_.reset(new int(42));
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+
+  // Check that if the variable changes, the EvaluationContext keeps returning
+  // the cached value.
+  fake_int_var_.reset(new int(5));
+
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  ASSERT_NE(nullptr, p_fake_int);
+  EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCachesNull) {
+  const int* p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  EXPECT_EQ(nullptr, p_fake_int);
+
+  fake_int_var_.reset(new int(42));
+  // A second attempt to read the variable should not work because this
+  // EvaluationContext already got a null value.
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  EXPECT_EQ(nullptr, p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueMixedTypes) {
+  const int* p_fake_int;
+  const string* p_fake_string;
+
+  fake_int_var_.reset(new int(42));
+  fake_poll_var_.reset(new string("Hello world!"));
+  // Check that the EvaluationContext can handle multiple Variable types. This
+  // is mostly a compile-time check due to the template nature of this method.
+  p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+  p_fake_string = eval_ctx_->GetValue(&fake_poll_var_);
+
+  ASSERT_NE(nullptr, p_fake_int);
+  EXPECT_EQ(42, *p_fake_int);
+
+  ASSERT_NE(nullptr, p_fake_string);
+  EXPECT_EQ("Hello world!", *p_fake_string);
+}
+
+// Test that we don't schedule an event if there's no variable to wait for.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithoutVariables) {
+  fake_const_var_.reset(new string("Hello world!"));
+  EXPECT_EQ(*eval_ctx_->GetValue(&fake_const_var_), "Hello world!");
+
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+// Test that reevaluation occurs when an async variable it depends on changes.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithVariables) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Check that the scheduled callback isn't run until we signal a ValueChaged.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_FALSE(value);
+
+  fake_async_var_.NotifyValueChanged();
+  EXPECT_FALSE(value);
+  // Ensure that the scheduled callback isn't run until we are back on the main
+  // loop.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_TRUE(value);
+}
+
+// Test that we don't re-schedule the events if we are attending one.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutCalledTwice) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+
+  // The scheduled event should still work.
+  fake_async_var_.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_TRUE(value);
+}
+
+// Test that reevaluation occurs when a polling timeout fires.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutRunsFromTimeout) {
+  fake_poll_var_.reset(new string("Polled value"));
+  eval_ctx_->GetValue(&fake_poll_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Check that the scheduled callback isn't run until the timeout occurs.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_FALSE(value);
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &value));
+  EXPECT_TRUE(value);
+}
+
+// Test that callback is called when evaluation context expires, and that it
+// cannot be used again unless the expiration deadline is reset.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutExpires) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Check that the scheduled callback isn't run until the timeout occurs.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_FALSE(value);
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &value));
+  EXPECT_TRUE(value);
+
+  // Ensure that we cannot reschedule an evaluation.
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+
+  // Ensure that we can reschedule an evaluation after resetting expiration.
+  eval_ctx_->ResetExpiration();
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+// Test that we clear the events when destroying the EvaluationContext.
+TEST_F(UmEvaluationContextTest, RemoveObserversAndTimeoutTest) {
+  fake_async_var_.reset(new string("Async value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  eval_ctx_ = nullptr;
+
+  // This should not trigger the callback since the EvaluationContext waiting
+  // for it is gone, and it should have remove all its observers.
+  fake_async_var_.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_FALSE(value);
+}
+
+// Scheduling two reevaluations from the callback should succeed.
+TEST_F(UmEvaluationContextTest,
+       RunOnValueChangeOrTimeoutReevaluatesRepeatedly) {
+  fake_poll_var_.reset(new string("Polled value"));
+  Closure evaluation = Bind(ReadVar<string>, eval_ctx_, &fake_poll_var_);
+  int num_reevaluations = 2;
+  bool done = false;
+
+  // Run the evaluation once.
+  evaluation.Run();
+
+  // Schedule repeated reevaluations.
+  Closure closure = Bind(EvaluateRepeatedly, evaluation, eval_ctx_,
+                         &num_reevaluations, &done);
+  ASSERT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(closure));
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &done));
+  EXPECT_EQ(0, num_reevaluations);
+}
+
+// Test that we can delete the EvaluationContext while having pending events.
+TEST_F(UmEvaluationContextTest, ObjectDeletedWithPendingEventsTest) {
+  fake_async_var_.reset(new string("Async value"));
+  fake_poll_var_.reset(new string("Polled value"));
+  eval_ctx_->GetValue(&fake_async_var_);
+  eval_ctx_->GetValue(&fake_poll_var_);
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+  // TearDown() checks for leaked observers on this async_variable, which means
+  // that our object is still alive after removing its reference.
+}
+
+// Test that timed events fired after removal of the EvaluationContext don't
+// crash.
+TEST_F(UmEvaluationContextTest, TimeoutEventAfterDeleteTest) {
+  FakeVariable<string> fake_short_poll_var = {"fake_short_poll",
+                                              TimeDelta::FromSeconds(1)};
+  fake_short_poll_var.reset(new string("Polled value"));
+  eval_ctx_->GetValue(&fake_short_poll_var);
+  bool value = false;
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+  // Remove the last reference to the EvaluationContext and run the loop for
+  // 10 seconds to give time to the main loop to trigger the timeout Event (of 1
+  // second). Our callback should not be called because the EvaluationContext
+  // was removed before the timeout event is attended.
+  eval_ctx_ = nullptr;
+  MessageLoopRunUntil(MessageLoop::current(),
+                      TimeDelta::FromSeconds(10),
+                      Bind(&GetBoolean, &value));
+  EXPECT_FALSE(value);
+}
+
+TEST_F(UmEvaluationContextTest, DefaultTimeout) {
+  // Test that the evaluation timeout calculation uses the default timeout on
+  // setup.
+  EXPECT_CALL(mock_var_async_, GetValue(default_timeout_, _))
+      .WillOnce(Return(nullptr));
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, TimeoutUpdatesWithMonotonicTime) {
+  fake_clock_.SetMonotonicTime(
+      fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1));
+
+  TimeDelta timeout = default_timeout_ - TimeDelta::FromSeconds(1);
+
+  EXPECT_CALL(mock_var_async_, GetValue(timeout, _))
+      .WillOnce(Return(nullptr));
+  EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) {
+  Time cur_time = fake_clock_.GetWallclockTime();
+  // Advance the time on the clock but don't call ResetEvaluation yet.
+  fake_clock_.SetWallclockTime(cur_time + TimeDelta::FromSeconds(4));
+
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+  // Call ResetEvaluation now, which should use the new evaluation time.
+  eval_ctx_->ResetEvaluation();
+
+  cur_time = fake_clock_.GetWallclockTime();
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) {
+  Time cur_time = fake_clock_.GetMonotonicTime();
+  // Advance the time on the clock but don't call ResetEvaluation yet.
+  fake_clock_.SetMonotonicTime(cur_time + TimeDelta::FromSeconds(4));
+
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+  // Call ResetEvaluation now, which should use the new evaluation time.
+  eval_ctx_->ResetEvaluation();
+
+  cur_time = fake_clock_.GetMonotonicTime();
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsWallclockTimeGreaterThanSignalsTriggerReevaluation) {
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+      fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(1)));
+
+  // The "false" from IsWallclockTimeGreaterThan means that's not that timestamp
+  // yet, so this should schedule a callback for when that happens.
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsMonotonicTimeGreaterThanSignalsTriggerReevaluation) {
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1)));
+
+  // The "false" from IsMonotonicTimeGreaterThan means that's not that timestamp
+  // yet, so this should schedule a callback for when that happens.
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsWallclockTimeGreaterThanDoesntRecordPastTimestamps) {
+  // IsWallclockTimeGreaterThan() should ignore timestamps on the past for
+  // reevaluation.
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(20)));
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+      fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)));
+
+  // Callback should not be scheduled.
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsMonotonicTimeGreaterThanDoesntRecordPastTimestamps) {
+  // IsMonotonicTimeGreaterThan() should ignore timestamps on the past for
+  // reevaluation.
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(20)));
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(1)));
+
+  // Callback should not be scheduled.
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest, DumpContext) {
+  // |fail_var_| yield "(no value)" since it is unset.
+  eval_ctx_->GetValue(&fail_var_);
+
+  // Check that this is included.
+  fake_int_var_.reset(new int(42));
+  eval_ctx_->GetValue(&fake_int_var_);
+
+  // Check that double-quotes are escaped properly.
+  fake_poll_var_.reset(new string("Hello \"world\"!"));
+  eval_ctx_->GetValue(&fake_poll_var_);
+
+  // Note that the variables are printed in alphabetical order. Also
+  // see UmEvaluationContextText::SetUp() where the values used for
+  // |evaluation_start_{monotonic,wallclock| are set.
+  EXPECT_EQ("{\n"
+            "   \"evaluation_start_monotonic\": \"4/22/2009 19:25:00 GMT\",\n"
+            "   \"evaluation_start_wallclock\": \"3/2/2006 1:23:45 GMT\",\n"
+            "   \"variables\": {\n"
+            "      \"fail_var\": \"(no value)\",\n"
+            "      \"fake_int\": \"42\",\n"
+            "      \"fake_poll\": \"Hello \\\"world\\\"!\"\n"
+            "   }\n"
+            "}",
+            eval_ctx_->DumpContext());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/fake_config_provider.h b/update_manager/fake_config_provider.h
new file mode 100644
index 0000000..5624943
--- /dev/null
+++ b/update_manager/fake_config_provider.h
@@ -0,0 +1,31 @@
+// Copyright 2014 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_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ConfigProvider base class.
+class FakeConfigProvider : public ConfigProvider {
+ public:
+  FakeConfigProvider() {}
+
+  FakeVariable<bool>* var_is_oobe_enabled() override {
+    return &var_is_oobe_enabled_;
+  }
+
+ private:
+  FakeVariable<bool> var_is_oobe_enabled_{  // NOLINT(whitespace/braces)
+      "is_oobe_enabled", kVariableModeConst};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeConfigProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
diff --git a/update_manager/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h
new file mode 100644
index 0000000..5819c43
--- /dev/null
+++ b/update_manager/fake_device_policy_provider.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the DevicePolicyProvider base class.
+class FakeDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+  FakeDevicePolicyProvider() {}
+
+  FakeVariable<bool>* var_device_policy_is_loaded() override {
+    return &var_device_policy_is_loaded_;
+  }
+
+  FakeVariable<std::string>* var_release_channel() override {
+    return &var_release_channel_;
+  }
+
+  FakeVariable<bool>* var_release_channel_delegated() override {
+    return &var_release_channel_delegated_;
+  }
+
+  FakeVariable<bool>* var_update_disabled() override {
+    return &var_update_disabled_;
+  }
+
+  FakeVariable<std::string>* var_target_version_prefix() override {
+    return &var_target_version_prefix_;
+  }
+
+  FakeVariable<base::TimeDelta>* var_scatter_factor() override {
+    return &var_scatter_factor_;
+  }
+
+  FakeVariable<std::set<ConnectionType>>*
+      var_allowed_connection_types_for_update() override {
+    return &var_allowed_connection_types_for_update_;
+  }
+
+  FakeVariable<std::string>* var_owner() override {
+    return &var_owner_;
+  }
+
+  FakeVariable<bool>* var_http_downloads_enabled() override {
+    return &var_http_downloads_enabled_;
+  }
+
+  FakeVariable<bool>* var_au_p2p_enabled() override {
+    return &var_au_p2p_enabled_;
+  }
+
+ private:
+  FakeVariable<bool> var_device_policy_is_loaded_{
+      "policy_is_loaded", kVariableModePoll};
+  FakeVariable<std::string> var_release_channel_{
+      "release_channel", kVariableModePoll};
+  FakeVariable<bool> var_release_channel_delegated_{
+      "release_channel_delegated", kVariableModePoll};
+  FakeVariable<bool> var_update_disabled_{
+      "update_disabled", kVariableModePoll};
+  FakeVariable<std::string> var_target_version_prefix_{
+      "target_version_prefix", kVariableModePoll};
+  FakeVariable<base::TimeDelta> var_scatter_factor_{
+      "scatter_factor", kVariableModePoll};
+  FakeVariable<std::set<ConnectionType>>
+      var_allowed_connection_types_for_update_{
+          "allowed_connection_types_for_update", kVariableModePoll};
+  FakeVariable<std::string> var_owner_{"owner", kVariableModePoll};
+  FakeVariable<bool> var_http_downloads_enabled_{
+      "http_downloads_enabled", kVariableModePoll};
+  FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/fake_random_provider.h b/update_manager/fake_random_provider.h
new file mode 100644
index 0000000..9f2bc35
--- /dev/null
+++ b/update_manager/fake_random_provider.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the RandomProvider base class.
+class FakeRandomProvider : public RandomProvider {
+ public:
+  FakeRandomProvider() {}
+
+  FakeVariable<uint64_t>* var_seed() override { return &var_seed_; }
+
+ private:
+  FakeVariable<uint64_t> var_seed_{"seed", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeRandomProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
diff --git a/update_manager/fake_shill_provider.h b/update_manager/fake_shill_provider.h
new file mode 100644
index 0000000..12cfe1d
--- /dev/null
+++ b/update_manager/fake_shill_provider.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ShillProvider base class.
+class FakeShillProvider : public ShillProvider {
+ public:
+  FakeShillProvider() {}
+
+  FakeVariable<bool>* var_is_connected() override {
+    return &var_is_connected_;
+  }
+
+  FakeVariable<ConnectionType>* var_conn_type() override {
+    return &var_conn_type_;
+  }
+
+  FakeVariable<ConnectionTethering>*
+      var_conn_tethering() override {
+    return &var_conn_tethering_;
+  }
+
+  FakeVariable<base::Time>* var_conn_last_changed() override {
+    return &var_conn_last_changed_;
+  }
+
+ private:
+  FakeVariable<bool> var_is_connected_{"is_connected", kVariableModePoll};
+  FakeVariable<ConnectionType> var_conn_type_{"conn_type", kVariableModePoll};
+  FakeVariable<ConnectionTethering> var_conn_tethering_{
+      "conn_tethering", kVariableModePoll};
+  FakeVariable<base::Time> var_conn_last_changed_{
+      "conn_last_changed", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeShillProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
diff --git a/update_manager/fake_state.h b/update_manager/fake_state.h
new file mode 100644
index 0000000..b803890
--- /dev/null
+++ b/update_manager/fake_state.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
+
+#include "update_engine/update_manager/fake_config_provider.h"
+#include "update_engine/update_manager/fake_device_policy_provider.h"
+#include "update_engine/update_manager/fake_random_provider.h"
+#include "update_engine/update_manager/fake_shill_provider.h"
+#include "update_engine/update_manager/fake_system_provider.h"
+#include "update_engine/update_manager/fake_time_provider.h"
+#include "update_engine/update_manager/fake_updater_provider.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// A fake State class that creates fake providers for all the providers.
+// This fake can be used in unit testing of Policy subclasses. To fake out the
+// value a variable is exposing, just call FakeVariable<T>::SetValue() on the
+// variable you fake out. For example:
+//
+//   FakeState fake_state_;
+//   fake_state_.random_provider_->var_seed()->SetValue(new uint64_t(12345));
+//
+// You can call SetValue more than once and the FakeVariable will take care of
+// the memory, but only the last value will remain.
+class FakeState : public State {
+ public:
+  // Creates and initializes the FakeState using fake providers.
+  FakeState() {}
+
+  ~FakeState() override {}
+
+  // Downcasted getters to access the fake instances during testing.
+  FakeConfigProvider* config_provider() override {
+    return &config_provider_;
+  }
+
+  FakeDevicePolicyProvider* device_policy_provider() override {
+    return &device_policy_provider_;
+  }
+
+  FakeRandomProvider* random_provider() override {
+    return &random_provider_;
+  }
+
+  FakeShillProvider* shill_provider() override {
+    return &shill_provider_;
+  }
+
+  FakeSystemProvider* system_provider() override {
+    return &system_provider_;
+  }
+
+  FakeTimeProvider* time_provider() override {
+    return &time_provider_;
+  }
+
+  FakeUpdaterProvider* updater_provider() override {
+    return &updater_provider_;
+  }
+
+ private:
+  FakeConfigProvider config_provider_;
+  FakeDevicePolicyProvider device_policy_provider_;
+  FakeRandomProvider random_provider_;
+  FakeShillProvider shill_provider_;
+  FakeSystemProvider system_provider_;
+  FakeTimeProvider time_provider_;
+  FakeUpdaterProvider updater_provider_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeState);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
diff --git a/update_manager/fake_system_provider.h b/update_manager/fake_system_provider.h
new file mode 100644
index 0000000..6b7151b
--- /dev/null
+++ b/update_manager/fake_system_provider.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the SystemProvider base class.
+class FakeSystemProvider : public SystemProvider {
+ public:
+  FakeSystemProvider() {}
+
+  FakeVariable<bool>* var_is_normal_boot_mode() override {
+    return &var_is_normal_boot_mode_;
+  }
+
+  FakeVariable<bool>* var_is_official_build() override {
+    return &var_is_official_build_;
+  }
+
+  FakeVariable<bool>* var_is_oobe_complete() override {
+    return &var_is_oobe_complete_;
+  }
+
+  FakeVariable<bool>* var_is_boot_device_removable() override {
+    return &var_is_boot_device_removable_;
+  }
+
+ private:
+  FakeVariable<bool> var_is_normal_boot_mode_{  // NOLINT(whitespace/braces)
+    "is_normal_boot_mode", kVariableModeConst};
+  FakeVariable<bool> var_is_official_build_{  // NOLINT(whitespace/braces)
+    "is_official_build", kVariableModeConst};
+  FakeVariable<bool> var_is_oobe_complete_{  // NOLINT(whitespace/braces)
+    "is_oobe_complete", kVariableModePoll};
+  FakeVariable<bool>
+      var_is_boot_device_removable_{  // NOLINT(whitespace/braces)
+        "is_boot_device_removable", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
diff --git a/update_manager/fake_time_provider.h b/update_manager/fake_time_provider.h
new file mode 100644
index 0000000..a60405b
--- /dev/null
+++ b/update_manager/fake_time_provider.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the TimeProvider base class.
+class FakeTimeProvider : public TimeProvider {
+ public:
+  FakeTimeProvider() {}
+
+  FakeVariable<base::Time>* var_curr_date() override { return &var_curr_date_; }
+  FakeVariable<int>* var_curr_hour() override { return &var_curr_hour_; }
+
+ private:
+  FakeVariable<base::Time> var_curr_date_{"curr_date", kVariableModePoll};
+  FakeVariable<int> var_curr_hour_{"curr_hour", kVariableModePoll};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeTimeProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
diff --git a/update_manager/fake_update_manager.h b/update_manager/fake_update_manager.h
new file mode 100644
index 0000000..34a3813
--- /dev/null
+++ b/update_manager/fake_update_manager.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+
+#include "update_engine/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/fake_state.h"
+
+namespace chromeos_update_manager {
+
+class FakeUpdateManager : public UpdateManager {
+ public:
+  explicit FakeUpdateManager(chromeos_update_engine::ClockInterface* clock)
+      : UpdateManager(clock, base::TimeDelta::FromSeconds(5),
+                      base::TimeDelta::FromHours(1), new FakeState()) {
+    // The FakeUpdateManager uses a DefaultPolicy.
+    set_policy(new DefaultPolicy(clock));
+  }
+
+  // UpdateManager overrides.
+  using UpdateManager::set_policy;
+
+  FakeState* state() {
+    return reinterpret_cast<FakeState*>(UpdateManager::state());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FakeUpdateManager);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
diff --git a/update_manager/fake_updater_provider.h b/update_manager/fake_updater_provider.h
new file mode 100644
index 0000000..0820858
--- /dev/null
+++ b/update_manager/fake_updater_provider.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the UpdaterProvider base class.
+class FakeUpdaterProvider : public UpdaterProvider {
+ public:
+  FakeUpdaterProvider() {}
+
+  FakeVariable<base::Time>* var_updater_started_time() override {
+    return &var_updater_started_time_;
+  }
+
+  FakeVariable<base::Time>* var_last_checked_time() override {
+    return &var_last_checked_time_;
+  }
+
+  FakeVariable<base::Time>* var_update_completed_time() override {
+    return &var_update_completed_time_;
+  }
+
+  FakeVariable<double>* var_progress() override {
+    return &var_progress_;
+  }
+
+  FakeVariable<Stage>* var_stage() override {
+    return &var_stage_;
+  }
+
+  FakeVariable<std::string>* var_new_version() override {
+    return &var_new_version_;
+  }
+
+  FakeVariable<int64_t>* var_payload_size() override {
+    return &var_payload_size_;
+  }
+
+  FakeVariable<std::string>* var_curr_channel() override {
+    return &var_curr_channel_;
+  }
+
+  FakeVariable<std::string>* var_new_channel() override {
+    return &var_new_channel_;
+  }
+
+  FakeVariable<bool>* var_p2p_enabled() override {
+    return &var_p2p_enabled_;
+  }
+
+  FakeVariable<bool>* var_cellular_enabled() override {
+    return &var_cellular_enabled_;
+  }
+
+  FakeVariable<unsigned int>* var_consecutive_failed_update_checks() override {
+    return &var_consecutive_failed_update_checks_;
+  }
+
+  FakeVariable<unsigned int>* var_server_dictated_poll_interval() override {
+    return &var_server_dictated_poll_interval_;
+  }
+
+  FakeVariable<UpdateRequestStatus>* var_forced_update_requested() override {
+    return &var_forced_update_requested_;
+  }
+
+ private:
+  FakeVariable<base::Time>
+      var_updater_started_time_{  // NOLINT(whitespace/braces)
+    "updater_started_time", kVariableModePoll};
+  FakeVariable<base::Time> var_last_checked_time_{  // NOLINT(whitespace/braces)
+    "last_checked_time", kVariableModePoll};
+  FakeVariable<base::Time>
+      var_update_completed_time_{  // NOLINT(whitespace/braces)
+    "update_completed_time", kVariableModePoll};
+  FakeVariable<double> var_progress_{  // NOLINT(whitespace/braces)
+    "progress", kVariableModePoll};
+  FakeVariable<Stage> var_stage_{  // NOLINT(whitespace/braces)
+    "stage", kVariableModePoll};
+  FakeVariable<std::string> var_new_version_{  // NOLINT(whitespace/braces)
+    "new_version", kVariableModePoll};
+  FakeVariable<int64_t> var_payload_size_{  // NOLINT(whitespace/braces)
+    "payload_size", kVariableModePoll};
+  FakeVariable<std::string> var_curr_channel_{  // NOLINT(whitespace/braces)
+    "curr_channel", kVariableModePoll};
+  FakeVariable<std::string> var_new_channel_{  // NOLINT(whitespace/braces)
+    "new_channel", kVariableModePoll};
+  FakeVariable<bool> var_p2p_enabled_{  // NOLINT(whitespace/braces)
+    "p2p_enabled", kVariableModePoll};
+  FakeVariable<bool> var_cellular_enabled_{  // NOLINT(whitespace/braces)
+    "cellular_enabled", kVariableModePoll};
+  FakeVariable<unsigned int>
+      var_consecutive_failed_update_checks_{  // NOLINT(whitespace/braces)
+    "consecutive_failed_update_checks", kVariableModePoll};
+  FakeVariable<unsigned int>
+      var_server_dictated_poll_interval_{  // NOLINT(whitespace/braces)
+    "server_dictated_poll_interval", kVariableModePoll};
+  FakeVariable<UpdateRequestStatus>
+      var_forced_update_requested_{  // NOLINT(whitespace/braces)
+    "forced_update_requested", kVariableModeAsync};
+
+  DISALLOW_COPY_AND_ASSIGN(FakeUpdaterProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
diff --git a/update_manager/fake_variable.h b/update_manager/fake_variable.h
new file mode 100644
index 0000000..9e51f7b
--- /dev/null
+++ b/update_manager/fake_variable.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_FAKE_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A fake typed variable to use while testing policy implementations. The
+// variable can be instructed to return any object of its type.
+template<typename T>
+class FakeVariable : public Variable<T> {
+ public:
+  FakeVariable(const std::string& name, VariableMode mode)
+      : Variable<T>(name, mode) {}
+  FakeVariable(const std::string& name, base::TimeDelta poll_interval)
+      : Variable<T>(name, poll_interval) {}
+  ~FakeVariable() override {}
+
+  // Sets the next value of this variable to the passed |p_value| pointer. Once
+  // returned by GetValue(), the pointer is released and has to be set again.
+  // A value of null means that the GetValue() call will fail and return
+  // null.
+  void reset(const T* p_value) {
+    ptr_.reset(p_value);
+  }
+
+  // Make the NotifyValueChanged() public for FakeVariables.
+  void NotifyValueChanged() {
+    Variable<T>::NotifyValueChanged();
+  }
+
+ protected:
+  // Variable<T> overrides.
+  // Returns the pointer set with reset(). The ownership of the object is passed
+  // to the caller and the pointer is release from the FakeVariable. A second
+  // call to GetValue() without reset() will return null and set the error
+  // message.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* errmsg) override {
+    if (ptr_ == nullptr && errmsg != nullptr)
+      *errmsg = this->GetName() + " is an empty FakeVariable";
+    // Passes the pointer ownership to the caller.
+    return ptr_.release();
+  }
+
+ private:
+  // The pointer returned by GetValue().
+  std::unique_ptr<const T> ptr_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeVariable);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
diff --git a/update_manager/generic_variables.h b/update_manager/generic_variables.h
new file mode 100644
index 0000000..b1b059f
--- /dev/null
+++ b/update_manager/generic_variables.h
@@ -0,0 +1,206 @@
+// Copyright (c) 2014 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.
+
+// Generic and provider-independent Variable subclasses. These variables can be
+// used by any state provider to implement simple variables to avoid repeat the
+// same common code on different state providers.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+
+#include <string>
+
+#include <base/callback.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Variable class returning a copy of a given object using the copy constructor.
+// This template class can be used to define variables that expose as a variable
+// any fixed object, such as the a provider's private member. The variable will
+// create copies of the provided object using the copy constructor of that
+// class.
+//
+// For example, a state provider exposing a private member as a variable can
+// implement this as follows:
+//
+//   class SomethingProvider {
+//    public:
+//      SomethingProvider(...) {
+//        var_something_foo = new PollCopyVariable<MyType>(foo_);
+//      }
+//      ...
+//    private:
+//     MyType foo_;
+//   };
+template<typename T>
+class PollCopyVariable : public Variable<T> {
+ public:
+  // Creates the variable returning copies of the passed |ref|. The reference to
+  // this object is kept and it should be available whenever the GetValue()
+  // method is called. If |is_set_p| is not null, then this flag will be
+  // consulted prior to returning the value, and an |errmsg| will be returned if
+  // it is not set.
+  PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p,
+                   const std::string& errmsg)
+      : Variable<T>(name, kVariableModePoll), ref_(ref), is_set_p_(is_set_p),
+        errmsg_(errmsg) {}
+  PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p)
+      : PollCopyVariable(name, ref, is_set_p, std::string()) {}
+  PollCopyVariable(const std::string& name, const T& ref)
+      : PollCopyVariable(name, ref, nullptr) {}
+
+  PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+                   const T& ref, const bool* is_set_p,
+                   const std::string& errmsg)
+      : Variable<T>(name, poll_interval), ref_(ref), is_set_p_(is_set_p),
+        errmsg_(errmsg) {}
+  PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+                   const T& ref, const bool* is_set_p)
+      : PollCopyVariable(name, poll_interval, ref, is_set_p, std::string()) {}
+  PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+                   const T& ref)
+      : PollCopyVariable(name, poll_interval, ref, nullptr) {}
+
+ protected:
+  FRIEND_TEST(UmPollCopyVariableTest, SimpleTest);
+  FRIEND_TEST(UmPollCopyVariableTest, UseCopyConstructorTest);
+
+  // Variable override.
+  inline const T* GetValue(base::TimeDelta /* timeout */,
+                           std::string* errmsg) override {
+    if (is_set_p_ && !(*is_set_p_)) {
+      if (errmsg) {
+        if (errmsg_.empty())
+          *errmsg = "No value set for " + this->GetName();
+        else
+          *errmsg = errmsg_;
+      }
+      return nullptr;
+    }
+    return new T(ref_);
+  }
+
+ private:
+  // Reference to the object to be copied by GetValue().
+  const T& ref_;
+
+  // A pointer to a flag indicating whether the value is set. If null, then the
+  // value is assumed to be set.
+  const bool* const is_set_p_;
+
+  // An error message to be returned when attempting to get an unset value.
+  const std::string errmsg_;
+};
+
+// Variable class returning a constant value that is cached on the variable when
+// it is created.
+template<typename T>
+class ConstCopyVariable : public Variable<T> {
+ public:
+  // Creates the variable returning copies of the passed |obj|. The value passed
+  // is copied in this variable, and new copies of it will be returned by
+  // GetValue().
+  ConstCopyVariable(const std::string& name, const T& obj)
+      : Variable<T>(name, kVariableModeConst), obj_(obj) {}
+
+ protected:
+  // Variable override.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* /* errmsg */) override {
+    return new T(obj_);
+  }
+
+ private:
+  // Value to be copied by GetValue().
+  const T obj_;
+};
+
+// Variable class returning a copy of a value returned by a given function. The
+// function is called every time the variable is being polled.
+template<typename T>
+class CallCopyVariable : public Variable<T> {
+ public:
+  CallCopyVariable(const std::string& name, base::Callback<T(void)> func)
+      : Variable<T>(name, kVariableModePoll), func_(func) {}
+  CallCopyVariable(const std::string& name,
+                   const base::TimeDelta poll_interval,
+                   base::Callback<T(void)> func)
+      : Variable<T>(name, poll_interval), func_(func) {}
+
+ protected:
+  // Variable override.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* /* errmsg */) override {
+    if (func_.is_null())
+      return nullptr;
+    return new T(func_.Run());
+  }
+
+ private:
+  FRIEND_TEST(UmCallCopyVariableTest, SimpleTest);
+
+  // The function to be called, stored as a base::Callback.
+  base::Callback<T(void)> func_;
+
+  DISALLOW_COPY_AND_ASSIGN(CallCopyVariable);
+};
+
+
+// A Variable class to implement simple Async variables. It provides two methods
+// SetValue and UnsetValue to modify the current value of the variable and
+// notify the registered observers whenever the value changed.
+//
+// The type T needs to be copy-constructible, default-constructible and have an
+// operator== (to determine if the value changed), which makes this class
+// suitable for basic types.
+template<typename T>
+class AsyncCopyVariable : public Variable<T> {
+ public:
+  explicit AsyncCopyVariable(const std::string& name)
+      : Variable<T>(name, kVariableModeAsync), has_value_(false) {}
+
+  AsyncCopyVariable(const std::string& name, const T value)
+      : Variable<T>(name, kVariableModeAsync),
+        has_value_(true), value_(value) {}
+
+  void SetValue(const T& new_value) {
+    bool should_notify = !(has_value_ && new_value == value_);
+    value_ = new_value;
+    has_value_ = true;
+    if (should_notify)
+      this->NotifyValueChanged();
+  }
+
+  void UnsetValue() {
+    if (has_value_) {
+      has_value_ = false;
+      this->NotifyValueChanged();
+    }
+  }
+
+ protected:
+  // Variable override.
+  const T* GetValue(base::TimeDelta /* timeout */,
+                    std::string* errmsg) override {
+    if (!has_value_) {
+      if (errmsg)
+        *errmsg = "No value set for " + this->GetName();
+      return nullptr;
+    }
+    return new T(value_);
+  }
+
+ private:
+  // Whether the variable has a value set.
+  bool has_value_;
+
+  // Copy of the object to be returned by GetValue().
+  T value_;
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
diff --git a/update_manager/generic_variables_unittest.cc b/update_manager/generic_variables_unittest.cc
new file mode 100644
index 0000000..372670f
--- /dev/null
+++ b/update_manager/generic_variables_unittest.cc
@@ -0,0 +1,212 @@
+// Copyright (c) 2014 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/update_manager/generic_variables.h"
+
+#include <memory>
+
+#include <base/callback.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmPollCopyVariableTest : public ::testing::Test {};
+
+
+TEST_F(UmPollCopyVariableTest, SimpleTest) {
+  // Tests that copies are generated as intended.
+  int source = 5;
+  PollCopyVariable<int> var("var", source);
+
+  // Generate and validate a copy.
+  unique_ptr<const int> copy_1(var.GetValue(
+          UmTestUtils::DefaultTimeout(), nullptr));
+  ASSERT_NE(nullptr, copy_1.get());
+  EXPECT_EQ(5, *copy_1);
+
+  // Assign a different value to the source variable.
+  source = 42;
+
+  // Check that the content of the copy was not affected (distinct instance).
+  EXPECT_EQ(5, *copy_1);
+
+  // Generate and validate a second copy.
+  UmTestUtils::ExpectVariableHasValue(42, &var);
+}
+
+TEST_F(UmPollCopyVariableTest, SetFlagTest) {
+  // Tests that the set flag is being referred to as expected.
+  int source = 5;
+  bool is_set = false;
+  PollCopyVariable<int> var("var", source, &is_set);
+
+  // Flag marked unset, nothing should be returned.
+  UmTestUtils::ExpectVariableNotSet(&var);
+
+  // Flag marked set, we should be getting a value.
+  is_set = true;
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class CopyConstructorTestClass {
+ public:
+  CopyConstructorTestClass(void) : copied_(false) {}
+  CopyConstructorTestClass(const CopyConstructorTestClass& other)
+      : copied_(true), val_(other.val_ * 2) {}
+
+  // Tells if the instance was constructed using the copy-constructor.
+  const bool copied_;
+
+  // An auxiliary internal value.
+  int val_ = 0;
+};
+
+
+TEST_F(UmPollCopyVariableTest, UseCopyConstructorTest) {
+  // Ensures that CopyVariables indeed uses the copy constructor.
+  const CopyConstructorTestClass source;
+  ASSERT_FALSE(source.copied_);
+
+  PollCopyVariable<CopyConstructorTestClass> var("var", source);
+  unique_ptr<const CopyConstructorTestClass> copy(
+      var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+  ASSERT_NE(nullptr, copy.get());
+  EXPECT_TRUE(copy->copied_);
+}
+
+
+class UmConstCopyVariableTest : public ::testing::Test {};
+
+TEST_F(UmConstCopyVariableTest, SimpleTest) {
+  int source = 5;
+  ConstCopyVariable<int> var("var", source);
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+
+  // Ensure the value is cached.
+  source = 42;
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class UmCallCopyVariableTest : public ::testing::Test {};
+
+CopyConstructorTestClass test_func(CopyConstructorTestClass* obj) {
+  obj->val_++;  // So we can check that the function was called.
+  return *obj;
+}
+
+TEST_F(UmCallCopyVariableTest, SimpleTest) {
+  // Tests that the returned value is generated by copying the value returned by
+  // the function call.
+
+  CopyConstructorTestClass test_obj;
+  ASSERT_FALSE(test_obj.copied_);
+  test_obj.val_ = 5;
+
+  base::Callback<CopyConstructorTestClass(void)> cb = base::Bind(
+      test_func, &test_obj);
+  CallCopyVariable<CopyConstructorTestClass> var("var", cb);
+
+  unique_ptr<const CopyConstructorTestClass> copy(
+      var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+  EXPECT_EQ(6, test_obj.val_);  // Check that the function was called.
+  ASSERT_NE(nullptr, copy.get());
+  EXPECT_TRUE(copy->copied_);
+  EXPECT_EQ(12, copy->val_);  // Check that copying occurred once.
+}
+
+TEST_F(UmCallCopyVariableTest, NullTest) {
+  // Ensures that the variable returns null when the callback is null.
+
+  base::Callback<bool(void)> cb;
+  CallCopyVariable<bool> var("var", cb);
+  UmTestUtils::ExpectVariableNotSet(&var);
+}
+
+class UmAsyncCopyVariableTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  void TearDown() override {
+    // No remaining event on the main loop.
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+
+  chromeos::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmAsyncCopyVariableTest, ConstructorTest) {
+  AsyncCopyVariable<int> var("var");
+  UmTestUtils::ExpectVariableNotSet(&var);
+  EXPECT_EQ(kVariableModeAsync, var.GetMode());
+}
+
+TEST_F(UmAsyncCopyVariableTest, SetValueTest) {
+  AsyncCopyVariable<int> var("var");
+  var.SetValue(5);
+  UmTestUtils::ExpectVariableHasValue(5, &var);
+  // Execute all the pending observers.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+TEST_F(UmAsyncCopyVariableTest, UnsetValueTest) {
+  AsyncCopyVariable<int> var("var", 42);
+  var.UnsetValue();
+  UmTestUtils::ExpectVariableNotSet(&var);
+  // Execute all the pending observers.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+class CallCounterObserver : public BaseVariable::ObserverInterface {
+ public:
+  void ValueChanged(BaseVariable* variable) {
+    calls_count_++;
+  }
+
+  int calls_count_ = 0;
+};
+
+TEST_F(UmAsyncCopyVariableTest, ObserverCalledTest) {
+  AsyncCopyVariable<int> var("var", 42);
+  CallCounterObserver observer;
+  var.AddObserver(&observer);
+  EXPECT_EQ(0, observer.calls_count_);
+
+  // Check that a different value fires the notification.
+  var.SetValue(5);
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, observer.calls_count_);
+
+  // Check the same value doesn't.
+  var.SetValue(5);
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, observer.calls_count_);
+
+  // Check that unsetting a previously set value fires the notification.
+  var.UnsetValue();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(2, observer.calls_count_);
+
+  // Check that unsetting again doesn't.
+  var.UnsetValue();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(2, observer.calls_count_);
+
+  var.RemoveObserver(&observer);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/mock_policy.h b/update_manager/mock_policy.h
new file mode 100644
index 0000000..24a6ee8
--- /dev/null
+++ b/update_manager/mock_policy.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_MOCK_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// A mocked implementation of Policy.
+class MockPolicy : public Policy {
+ public:
+  explicit MockPolicy(chromeos_update_engine::ClockInterface* clock)
+      : default_policy_(clock) {
+    // We defer to the corresponding DefaultPolicy methods, by default.
+    ON_CALL(*this, UpdateCheckAllowed(testing::_, testing::_, testing::_,
+                                      testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::UpdateCheckAllowed));
+    ON_CALL(*this, UpdateCanStart(testing::_, testing::_, testing::_,
+                                  testing::_, testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::UpdateCanStart));
+    ON_CALL(*this, UpdateDownloadAllowed(testing::_, testing::_, testing::_,
+                                         testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::UpdateDownloadAllowed));
+    ON_CALL(*this, P2PEnabled(testing::_, testing::_, testing::_, testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::P2PEnabled));
+    ON_CALL(*this, P2PEnabledChanged(testing::_, testing::_, testing::_,
+                                     testing::_, testing::_))
+        .WillByDefault(testing::Invoke(
+                &default_policy_, &DefaultPolicy::P2PEnabledChanged));
+  }
+
+  MockPolicy() : MockPolicy(nullptr) {}
+  ~MockPolicy() override {}
+
+  // Policy overrides.
+  MOCK_CONST_METHOD4(UpdateCheckAllowed,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                UpdateCheckParams*));
+
+  MOCK_CONST_METHOD5(UpdateCanStart,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                UpdateDownloadParams*, UpdateState));
+
+  MOCK_CONST_METHOD4(UpdateDownloadAllowed,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                bool*));
+
+  MOCK_CONST_METHOD4(P2PEnabled,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                bool*));
+
+  MOCK_CONST_METHOD5(P2PEnabledChanged,
+                     EvalStatus(EvaluationContext*, State*, std::string*,
+                                bool*, bool));
+
+ protected:
+  // Policy override.
+  std::string PolicyName() const override { return "MockPolicy"; }
+
+ private:
+  DefaultPolicy default_policy_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockPolicy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
diff --git a/update_manager/mock_variable.h b/update_manager/mock_variable.h
new file mode 100644
index 0000000..6be13e5
--- /dev/null
+++ b/update_manager/mock_variable.h
@@ -0,0 +1,30 @@
+// Copyright 2014 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_UPDATE_MANAGER_MOCK_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// This is a generic mock of the Variable class.
+template<typename T>
+class MockVariable : public Variable<T> {
+ public:
+  using Variable<T>::Variable;
+
+  MOCK_METHOD2_T(GetValue, const T*(base::TimeDelta, std::string*));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockVariable);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
diff --git a/update_manager/policy.cc b/update_manager/policy.cc
new file mode 100644
index 0000000..5572974
--- /dev/null
+++ b/update_manager/policy.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2014 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/update_manager/policy.h"
+
+#include <string>
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+string ToString(EvalStatus status) {
+  switch (status) {
+    case EvalStatus::kFailed:
+      return "kFailed";
+    case EvalStatus::kSucceeded:
+      return "kSucceeded";
+    case EvalStatus::kAskMeAgainLater:
+      return "kAskMeAgainLater";
+  }
+  return "Invalid";
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/policy.h b/update_manager/policy.h
new file mode 100644
index 0000000..be30f26
--- /dev/null
+++ b/update_manager/policy.h
@@ -0,0 +1,278 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "update_engine/error_code.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// The three different results of a policy request.
+enum class EvalStatus {
+  kFailed,
+  kSucceeded,
+  kAskMeAgainLater,
+};
+
+std::string ToString(EvalStatus status);
+
+// Parameters of an update check. These parameters are determined by the
+// UpdateCheckAllowed policy.
+struct UpdateCheckParams {
+  bool updates_enabled;  // Whether the auto-updates are enabled on this build.
+
+  // Attributes pertaining to the case where update checks are allowed.
+  //
+  // A target version prefix, if imposed by policy; otherwise, an empty string.
+  std::string target_version_prefix;
+  // A target channel, if so imposed by policy; otherwise, an empty string.
+  std::string target_channel;
+
+  // Whether the allowed update is interactive (user-initiated) or periodic.
+  bool is_interactive;
+};
+
+// Input arguments to UpdateCanStart.
+//
+// A snapshot of the state of the current update process. This includes
+// everything that a policy might need and that occurred since the first time
+// the current payload was first seen and attempted (consecutively).
+struct UpdateState {
+  // Information pertaining to the current update payload and/or check.
+  //
+  // Whether the current update check is an interactive one. The caller should
+  // feed the value returned by the preceding call to UpdateCheckAllowed().
+  bool is_interactive;
+  // Whether it is a delta payload.
+  bool is_delta_payload;
+  // Wallclock time when payload was first (consecutively) offered by Omaha.
+  base::Time first_seen;
+  // Number of consecutive update checks returning the current update.
+  int num_checks;
+  // Number of update payload failures and the wallclock time when it was last
+  // updated by the updater. These should both be nullified whenever a new
+  // update is seen; they are updated at the policy's descretion (via
+  // UpdateDownloadParams.do_increment_failures) once all of the usable download
+  // URLs for the payload have been used without success. They should be
+  // persisted across reboots.
+  int num_failures;
+  base::Time failures_last_updated;
+
+  // Information pertaining to downloading and applying of the current update.
+  //
+  // An array of download URLs provided by Omaha.
+  std::vector<std::string> download_urls;
+  // Max number of errors allowed per download URL.
+  int download_errors_max;
+  // The index of the URL to download from, as determined in the previous call
+  // to the policy. For a newly seen payload, this should be -1.
+  int last_download_url_idx;
+  // The number of successive download errors pertaining to this last URL, as
+  // determined in the previous call to the policy. For a newly seen payload,
+  // this should be zero.
+  int last_download_url_num_errors;
+  // An array of errors that occurred while trying to download this update since
+  // the previous call to this policy has returned, or since this payload was
+  // first seen, or since the updater process has started (whichever is later).
+  // Includes the URL index attempted, the error code, and the wallclock-based
+  // timestamp when it occurred.
+  std::vector<std::tuple<int, chromeos_update_engine::ErrorCode, base::Time>>
+      download_errors;
+  // Whether Omaha forbids use of P2P for downloading and/or sharing.
+  bool p2p_downloading_disabled;
+  bool p2p_sharing_disabled;
+  // The number of P2P download attempts and wallclock-based time when P2P
+  // download was first attempted.
+  int p2p_num_attempts;
+  base::Time p2p_first_attempted;
+
+  // Information pertaining to update backoff mechanism.
+  //
+  // The currently known (persisted) wallclock-based backoff expiration time;
+  // zero if none.
+  base::Time backoff_expiry;
+  // Whether backoff is disabled by Omaha.
+  bool is_backoff_disabled;
+
+  // Information pertaining to update scattering.
+  //
+  // The currently knwon (persisted) scattering wallclock-based wait period and
+  // update check threshold; zero if none.
+  base::TimeDelta scatter_wait_period;
+  int scatter_check_threshold;
+  // Maximum wait period allowed for this update, as determined by Omaha.
+  base::TimeDelta scatter_wait_period_max;
+  // Minimum/maximum check threshold values.
+  // TODO(garnold) These appear to not be related to the current update and so
+  // should probably be obtained as variables via UpdaterProvider.
+  int scatter_check_threshold_min;
+  int scatter_check_threshold_max;
+};
+
+// Results regarding the downloading and applying of an update, as determined by
+// UpdateCanStart.
+//
+// An enumerator for the reasons of not allowing an update to start.
+enum class UpdateCannotStartReason {
+  kUndefined,
+  kCheckDue,
+  kScattering,
+  kBackoff,
+  kCannotDownload,
+};
+
+struct UpdateDownloadParams {
+  // Whether the update attempt is allowed to proceed.
+  bool update_can_start;
+  // If update cannot proceed, a reason code for why it cannot do so.
+  UpdateCannotStartReason cannot_start_reason;
+
+  // Download related attributes. The update engine uses them to choose the
+  // means for downloading and applying an update.
+  //
+  // The index of the download URL to use (-1 means no suitable URL was found)
+  // and whether it can be used. Even if there's no URL or its use is not
+  // allowed (backoff, scattering) there may still be other means for download
+  // (like P2P).  The URL index needs to be persisted and handed back to the
+  // policy on the next time it is called.
+  int download_url_idx;
+  bool download_url_allowed;
+  // The number of download errors associated with this download URL. This value
+  // needs to be persisted and handed back to the policy on the next time it is
+  // called.
+  int download_url_num_errors;
+  // Whether P2P download and sharing are allowed.
+  bool p2p_downloading_allowed;
+  bool p2p_sharing_allowed;
+
+  // Other values that need to be persisted and handed to the policy as need on
+  // the next call.
+  //
+  // Whether an update failure has been identified by the policy. The client
+  // should increment and persist its update failure count, and record the time
+  // when this was done; it needs to hand these values back to the policy
+  // (UpdateState.{num_failures,failures_last_updated}) on the next time it is
+  // called.
+  bool do_increment_failures;
+  // The current backof expiry.
+  base::Time backoff_expiry;
+  // The scattering wait period and check threshold.
+  base::TimeDelta scatter_wait_period;
+  int scatter_check_threshold;
+};
+
+// The Policy class is an interface to the ensemble of policy requests that the
+// client can make. A derived class includes the policy implementations of
+// these.
+//
+// When compile-time selection of the policy is required due to missing or extra
+// parts in a given platform, a different Policy subclass can be used.
+class Policy {
+ public:
+  virtual ~Policy() {}
+
+  // Returns the name of a public policy request.
+  // IMPORTANT: Be sure to add a conditional for each new public policy that is
+  // being added to this class in the future.
+  template<typename R, typename... Args>
+  std::string PolicyRequestName(
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          Args...) const) const {
+    std::string class_name = PolicyName() + "::";
+
+    if (reinterpret_cast<typeof(&Policy::UpdateCheckAllowed)>(
+            policy_method) == &Policy::UpdateCheckAllowed)
+      return class_name + "UpdateCheckAllowed";
+    if (reinterpret_cast<typeof(&Policy::UpdateCanStart)>(
+            policy_method) == &Policy::UpdateCanStart)
+      return class_name + "UpdateCanStart";
+    if (reinterpret_cast<typeof(&Policy::UpdateDownloadAllowed)>(
+            policy_method) == &Policy::UpdateDownloadAllowed)
+      return class_name + "UpdateDownloadAllowed";
+    if (reinterpret_cast<typeof(&Policy::P2PEnabled)>(
+            policy_method) == &Policy::P2PEnabled)
+      return class_name + "P2PEnabled";
+    if (reinterpret_cast<typeof(&Policy::P2PEnabledChanged)>(
+            policy_method) == &Policy::P2PEnabledChanged)
+      return class_name + "P2PEnabledChanged";
+
+    NOTREACHED();
+    return class_name + "(unknown)";
+  }
+
+
+  // List of policy requests. A policy request takes an EvaluationContext as the
+  // first argument, a State instance, a returned error message, a returned
+  // value and optionally followed by one or more arbitrary constant arguments.
+  //
+  // When the implementation fails, the method returns EvalStatus::kFailed and
+  // sets the |error| string.
+
+  // UpdateCheckAllowed returns whether it is allowed to request an update check
+  // to Omaha.
+  virtual EvalStatus UpdateCheckAllowed(
+      EvaluationContext* ec, State* state, std::string* error,
+      UpdateCheckParams* result) const = 0;
+
+  // Returns EvalStatus::kSucceeded if either an update can start being
+  // processed, or the attempt needs to be aborted. In cases where the update
+  // needs to wait for some condition to be satisfied, but none of the values
+  // that need to be persisted has changed, returns
+  // EvalStatus::kAskMeAgainLater. Arguments include an |update_state| that
+  // encapsulates data pertaining to the current ongoing update process.
+  virtual EvalStatus UpdateCanStart(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      UpdateDownloadParams* result,
+      UpdateState update_state) const = 0;
+
+  // Checks whether downloading of an update is allowed; currently, this checks
+  // whether the network connection type is suitable for updating over.  May
+  // consult the shill provider as well as the device policy (if available).
+  // Returns |EvalStatus::kSucceeded|, setting |result| according to whether or
+  // not the current connection can be used; on error, returns
+  // |EvalStatus::kFailed| and sets |error| accordingly.
+  virtual EvalStatus UpdateDownloadAllowed(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      bool* result) const = 0;
+
+  // Checks whether P2P is enabled. This may consult device policy and other
+  // global settings.
+  virtual EvalStatus P2PEnabled(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result) const = 0;
+
+  // Checks whether P2P is enabled, but blocks (returns
+  // |EvalStatus::kAskMeAgainLater|) until it is different from |prev_result|.
+  // If the P2P enabled status is not expected to change, will return
+  // immediately with |EvalStatus::kSucceeded|. This internally uses the
+  // P2PEnabled() policy above.
+  virtual EvalStatus P2PEnabledChanged(
+      EvaluationContext* ec, State* state, std::string* error,
+      bool* result, bool prev_result) const = 0;
+
+ protected:
+  Policy() {}
+
+  // Returns the name of the actual policy class.
+  virtual std::string PolicyName() const = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Policy);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
diff --git a/update_manager/policy_utils.h b/update_manager/policy_utils.h
new file mode 100644
index 0000000..d612f20
--- /dev/null
+++ b/update_manager/policy_utils.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_POLICY_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
+
+#include "update_engine/update_manager/policy.h"
+
+// Checks that the passed pointer value is not null, returning kFailed on the
+// current context and setting the *error description when it is null. The
+// intended use is to validate variable failures while using
+// EvaluationContext::GetValue, for example:
+//
+//   const int* my_value = ec->GetValue(state->my_provider()->var_my_value());
+//   POLICY_CHECK_VALUE_AND_FAIL(my_value, error);
+//
+#define POLICY_CHECK_VALUE_AND_FAIL(ptr, error) \
+    do { \
+      if ((ptr) == nullptr) { \
+        *(error) = #ptr " is required but is null."; \
+        return EvalStatus::kFailed; \
+      } \
+    } while (false)
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
diff --git a/update_manager/prng.h b/update_manager/prng.h
new file mode 100644
index 0000000..746e355
--- /dev/null
+++ b/update_manager/prng.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_PRNG_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
+
+#include <random>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+// A thread-safe, unsecure, 32-bit pseudo-random number generator based on
+// std::mt19937.
+class PRNG {
+ public:
+  // Initializes the generator with the passed |seed| value.
+  explicit PRNG(uint32_t seed) : gen_(seed) {}
+
+  // Returns a random unsigned 32-bit integer.
+  uint32_t Rand() { return gen_(); }
+
+  // Returns a random integer uniformly distributed in the range [min, max].
+  int RandMinMax(int min, int max) {
+    DCHECK_LE(min, max);
+    return std::uniform_int_distribution<>(min, max)(gen_);
+  }
+
+ private:
+  // A pseudo-random number generator.
+  std::mt19937 gen_;
+
+  DISALLOW_COPY_AND_ASSIGN(PRNG);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
diff --git a/update_manager/prng_unittest.cc b/update_manager/prng_unittest.cc
new file mode 100644
index 0000000..4d165b3
--- /dev/null
+++ b/update_manager/prng_unittest.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2014 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/update_manager/prng.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using std::vector;
+
+namespace chromeos_update_manager {
+
+TEST(UmPRNGTest, ShouldBeDeterministic) {
+  PRNG a(42);
+  PRNG b(42);
+
+  for (int i = 0; i < 1000; ++i) {
+    EXPECT_EQ(a.Rand(), b.Rand()) << "Iteration i=" << i;
+  }
+}
+
+TEST(UmPRNGTest, SeedChangesGeneratedSequence) {
+  PRNG a(42);
+  PRNG b(5);
+
+  vector<uint32_t> values_a;
+  vector<uint32_t> values_b;
+
+  for (int i = 0; i < 100; ++i) {
+    values_a.push_back(a.Rand());
+    values_b.push_back(b.Rand());
+  }
+  EXPECT_NE(values_a, values_b);
+}
+
+TEST(UmPRNGTest, IsNotConstant) {
+  PRNG prng(5);
+
+  uint32_t initial_value = prng.Rand();
+  bool prng_is_constant = true;
+  for (int i = 0; i < 100; ++i) {
+    if (prng.Rand() != initial_value) {
+      prng_is_constant = false;
+      break;
+    }
+  }
+  EXPECT_FALSE(prng_is_constant) << "After 100 iterations.";
+}
+
+TEST(UmPRNGTest, RandCoversRange) {
+  PRNG a(42);
+  int hits[11] = { 0 };
+
+  for (int i = 0; i < 1000; i++) {
+    int r = a.RandMinMax(0, 10);
+    ASSERT_LE(0, r);
+    ASSERT_GE(10, r);
+    hits[r]++;
+  }
+
+  for (auto& hit : hits)
+    EXPECT_LT(0, hit);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/provider.h b/update_manager/provider.h
new file mode 100644
index 0000000..aac92d4
--- /dev/null
+++ b/update_manager/provider.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// Abstract base class for a policy provider.
+class Provider {
+ public:
+  virtual ~Provider() {}
+
+ protected:
+  Provider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Provider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
diff --git a/update_manager/random_provider.h b/update_manager/random_provider.h
new file mode 100644
index 0000000..7c22863
--- /dev/null
+++ b/update_manager/random_provider.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider of random values.
+class RandomProvider : public Provider {
+ public:
+  ~RandomProvider() override {}
+
+  // Return a random number every time it is requested. Note that values
+  // returned by the variables are cached by the EvaluationContext, so the
+  // returned value will be the same during the same policy request. If more
+  // random values are needed use a PRNG seeded with this value.
+  virtual Variable<uint64_t>* var_seed() = 0;
+
+ protected:
+  RandomProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RandomProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
diff --git a/update_manager/real_config_provider.cc b/update_manager/real_config_provider.cc
new file mode 100644
index 0000000..ac334c4
--- /dev/null
+++ b/update_manager/real_config_provider.cc
@@ -0,0 +1,52 @@
+// Copyright 2014 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/update_manager/real_config_provider.h"
+
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <chromeos/key_value_store.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/utils.h"
+
+using chromeos::KeyValueStore;
+
+namespace {
+
+const char* kConfigFilePath = "/etc/update_manager.conf";
+
+// Config options:
+const char* kConfigOptsIsOOBEEnabled = "is_oobe_enabled";
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+bool RealConfigProvider::Init() {
+  KeyValueStore store;
+
+  if (hardware_->IsNormalBootMode()) {
+    store.Load(base::FilePath(root_prefix_ + kConfigFilePath));
+  } else {
+    if (store.Load(base::FilePath(root_prefix_ +
+                                  chromeos_update_engine::kStatefulPartition +
+                                  kConfigFilePath))) {
+      LOG(INFO) << "UpdateManager Config loaded from stateful partition.";
+    } else {
+      store.Load(base::FilePath(root_prefix_ + kConfigFilePath));
+    }
+  }
+
+  bool is_oobe_enabled;
+  if (!store.GetBoolean(kConfigOptsIsOOBEEnabled, &is_oobe_enabled))
+    is_oobe_enabled = true;  // Default value.
+  var_is_oobe_enabled_.reset(
+      new ConstCopyVariable<bool>(kConfigOptsIsOOBEEnabled, is_oobe_enabled));
+
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_config_provider.h b/update_manager/real_config_provider.h
new file mode 100644
index 0000000..82c9284
--- /dev/null
+++ b/update_manager/real_config_provider.h
@@ -0,0 +1,53 @@
+// Copyright 2014 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_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/hardware_interface.h"
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// ConfigProvider concrete implementation.
+class RealConfigProvider : public ConfigProvider {
+ public:
+  explicit RealConfigProvider(
+      chromeos_update_engine::HardwareInterface* hardware)
+      : hardware_(hardware) {}
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_is_oobe_enabled() override {
+    return var_is_oobe_enabled_.get();
+  }
+
+ private:
+  friend class UmRealConfigProviderTest;
+
+  // Used for testing. Sets the root prefix, which is by default "". Call this
+  // method before calling Init() in order to mock out the place where the files
+  // are being read from.
+  void SetRootPrefix(const std::string& prefix) {
+    root_prefix_ = prefix;
+  }
+
+  std::unique_ptr<ConstCopyVariable<bool>> var_is_oobe_enabled_;
+
+  chromeos_update_engine::HardwareInterface* hardware_;
+
+  // Prefix to prepend to the file paths. Useful for testing.
+  std::string root_prefix_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealConfigProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
diff --git a/update_manager/real_config_provider_unittest.cc b/update_manager/real_config_provider_unittest.cc
new file mode 100644
index 0000000..f6ddb7c
--- /dev/null
+++ b/update_manager/real_config_provider_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 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/update_manager/real_config_provider.h"
+
+#include <memory>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/constants.h"
+#include "update_engine/fake_hardware.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::TimeDelta;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealConfigProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(root_dir_.CreateUniqueTempDir());
+    provider_.reset(new RealConfigProvider(&fake_hardware_));
+    provider_->SetRootPrefix(root_dir_.path().value());
+  }
+
+  void WriteStatefulConfig(const string& config) {
+    base::FilePath kFile(root_dir_.path().value()
+                         + chromeos_update_engine::kStatefulPartition
+                         + "/etc/update_manager.conf");
+    ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+    ASSERT_TRUE(WriteFileString(kFile.value(), config));
+  }
+
+  void WriteRootfsConfig(const string& config) {
+    base::FilePath kFile(root_dir_.path().value()
+                         + "/etc/update_manager.conf");
+    ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+    ASSERT_TRUE(WriteFileString(kFile.value(), config));
+  }
+
+  unique_ptr<RealConfigProvider> provider_;
+  chromeos_update_engine::FakeHardware fake_hardware_;
+  TimeDelta default_timeout_ = TimeDelta::FromSeconds(1);
+  base::ScopedTempDir root_dir_;
+};
+
+TEST_F(UmRealConfigProviderTest, InitTest) {
+  EXPECT_TRUE(provider_->Init());
+  EXPECT_NE(nullptr, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, NoFileFoundReturnsDefault) {
+  EXPECT_TRUE(provider_->Init());
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, DontReadStatefulInNormalMode) {
+  fake_hardware_.SetIsNormalBootMode(true);
+  WriteStatefulConfig("is_oobe_enabled=false");
+
+  EXPECT_TRUE(provider_->Init());
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, ReadStatefulInDevMode) {
+  fake_hardware_.SetIsNormalBootMode(false);
+  WriteRootfsConfig("is_oobe_enabled=true");
+  // Since the stateful is present, this should read that one.
+  WriteStatefulConfig("is_oobe_enabled=false");
+
+  EXPECT_TRUE(provider_->Init());
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, ReadRootfsIfStatefulNotFound) {
+  fake_hardware_.SetIsNormalBootMode(false);
+  WriteRootfsConfig("is_oobe_enabled=false");
+
+  EXPECT_TRUE(provider_->Init());
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_enabled());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.cc b/update_manager/real_device_policy_provider.cc
new file mode 100644
index 0000000..6f484f7
--- /dev/null
+++ b/update_manager/real_device_policy_provider.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2014 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/update_manager/real_device_policy_provider.h"
+
+#include <stdint.h>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/glib_utils.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/real_shill_provider.h"
+#include "update_engine/utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using policy::DevicePolicy;
+using std::set;
+using std::string;
+
+namespace {
+
+const int kDevicePolicyRefreshRateInMinutes = 60;
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+RealDevicePolicyProvider::~RealDevicePolicyProvider() {
+  MessageLoop::current()->CancelTask(scheduled_refresh_);
+  // Detach signal handler, free manager proxy.
+  dbus_->ProxyDisconnectSignal(manager_proxy_,
+                               login_manager::kPropertyChangeCompleteSignal,
+                               G_CALLBACK(HandlePropertyChangedCompletedStatic),
+                               this);
+  dbus_->ProxyUnref(manager_proxy_);
+}
+
+bool RealDevicePolicyProvider::Init() {
+  CHECK(policy_provider_ != nullptr);
+
+  // On Init() we try to get the device policy and keep updating it.
+  RefreshDevicePolicyAndReschedule();
+
+  // We also listen for signals from the session manager to force a device
+  // policy refresh.
+  GError* error = nullptr;
+  DBusGConnection* connection = dbus_->BusGet(DBUS_BUS_SYSTEM, &error);
+  if (!connection) {
+    LOG(ERROR) << "Failed to initialize DBus connection: "
+               << chromeos_update_engine::utils::GetAndFreeGError(&error);
+    return false;
+  }
+  manager_proxy_ = dbus_->ProxyNewForName(
+      connection,
+      login_manager::kSessionManagerServiceName,
+      login_manager::kSessionManagerServicePath,
+      login_manager::kSessionManagerInterface);
+
+  // Subscribe to the session manager's PropertyChangeComplete signal.
+  dbus_->ProxyAddSignal_1(manager_proxy_,
+                          login_manager::kPropertyChangeCompleteSignal,
+                          G_TYPE_STRING);
+  dbus_->ProxyConnectSignal(manager_proxy_,
+                            login_manager::kPropertyChangeCompleteSignal,
+                            G_CALLBACK(HandlePropertyChangedCompletedStatic),
+                            this, nullptr);
+  return true;
+}
+
+// static
+void RealDevicePolicyProvider::HandlePropertyChangedCompletedStatic(
+    DBusGProxy* proxy, const char* /* payload */, void* data) {
+  // We refresh the policy file even if the payload string is kSignalFailure.
+  RealDevicePolicyProvider* policy_provider =
+      reinterpret_cast<RealDevicePolicyProvider*>(data);
+  LOG(INFO) << "Reloading device policy due to signal received.";
+  policy_provider->RefreshDevicePolicy();
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule() {
+  RefreshDevicePolicy();
+  scheduled_refresh_ = MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule,
+                 base::Unretained(this)),
+      TimeDelta::FromMinutes(kDevicePolicyRefreshRateInMinutes));
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+    AsyncCopyVariable<T>* var,
+    bool (DevicePolicy::*getter_method)(T*) const) {
+  T new_value;
+  if (policy_provider_->device_policy_is_loaded() &&
+      (policy_provider_->GetDevicePolicy().*getter_method)(&new_value)) {
+    var->SetValue(new_value);
+  } else {
+    var->UnsetValue();
+  }
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+    AsyncCopyVariable<T>* var,
+    bool (RealDevicePolicyProvider::*getter_method)(T*) const) {
+  T new_value;
+  if (policy_provider_->device_policy_is_loaded() &&
+      (this->*getter_method)(&new_value)) {
+    var->SetValue(new_value);
+  } else {
+    var->UnsetValue();
+  }
+}
+
+bool RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate(
+      set<ConnectionType>* allowed_types) const {
+  set<string> allowed_types_str;
+  if (!policy_provider_->GetDevicePolicy()
+      .GetAllowedConnectionTypesForUpdate(&allowed_types_str)) {
+    return false;
+  }
+  allowed_types->clear();
+  for (auto& type_str : allowed_types_str) {
+    ConnectionType type =
+        RealShillProvider::ParseConnectionType(type_str.c_str());
+    if (type != ConnectionType::kUnknown) {
+      allowed_types->insert(type);
+    } else {
+      LOG(WARNING) << "Policy includes unknown connection type: " << type_str;
+    }
+  }
+  return true;
+}
+
+bool RealDevicePolicyProvider::ConvertScatterFactor(
+    TimeDelta* scatter_factor) const {
+  int64_t scatter_factor_in_seconds;
+  if (!policy_provider_->GetDevicePolicy().GetScatterFactorInSeconds(
+      &scatter_factor_in_seconds)) {
+    return false;
+  }
+  if (scatter_factor_in_seconds < 0) {
+    LOG(WARNING) << "Ignoring negative scatter factor: "
+                 << scatter_factor_in_seconds;
+    return false;
+  }
+  *scatter_factor = TimeDelta::FromSeconds(scatter_factor_in_seconds);
+  return true;
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicy() {
+  if (!policy_provider_->Reload()) {
+    LOG(INFO) << "No device policies/settings present.";
+  }
+
+  var_device_policy_is_loaded_.SetValue(
+      policy_provider_->device_policy_is_loaded());
+
+  UpdateVariable(&var_release_channel_, &DevicePolicy::GetReleaseChannel);
+  UpdateVariable(&var_release_channel_delegated_,
+                 &DevicePolicy::GetReleaseChannelDelegated);
+  UpdateVariable(&var_update_disabled_, &DevicePolicy::GetUpdateDisabled);
+  UpdateVariable(&var_target_version_prefix_,
+                 &DevicePolicy::GetTargetVersionPrefix);
+  UpdateVariable(&var_scatter_factor_,
+                 &RealDevicePolicyProvider::ConvertScatterFactor);
+  UpdateVariable(
+      &var_allowed_connection_types_for_update_,
+      &RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate);
+  UpdateVariable(&var_owner_, &DevicePolicy::GetOwner);
+  UpdateVariable(&var_http_downloads_enabled_,
+                 &DevicePolicy::GetHttpDownloadsEnabled);
+  UpdateVariable(&var_au_p2p_enabled_, &DevicePolicy::GetAuP2PEnabled);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.h b/update_manager/real_device_policy_provider.h
new file mode 100644
index 0000000..b052d59
--- /dev/null
+++ b/update_manager/real_device_policy_provider.h
@@ -0,0 +1,150 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+#include <policy/libpolicy.h>
+
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// DevicePolicyProvider concrete implementation.
+class RealDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+  RealDevicePolicyProvider(
+      chromeos_update_engine::DBusWrapperInterface* const dbus,
+      policy::PolicyProvider* policy_provider)
+      : policy_provider_(policy_provider),
+        dbus_(dbus) {}
+  ~RealDevicePolicyProvider();
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_device_policy_is_loaded() override {
+    return &var_device_policy_is_loaded_;
+  }
+
+  Variable<std::string>* var_release_channel() override {
+    return &var_release_channel_;
+  }
+
+  Variable<bool>* var_release_channel_delegated() override {
+    return &var_release_channel_delegated_;
+  }
+
+  Variable<bool>* var_update_disabled() override {
+    return &var_update_disabled_;
+  }
+
+  Variable<std::string>* var_target_version_prefix() override {
+    return &var_target_version_prefix_;
+  }
+
+  Variable<base::TimeDelta>* var_scatter_factor() override {
+    return &var_scatter_factor_;
+  }
+
+  Variable<std::set<ConnectionType>>*
+      var_allowed_connection_types_for_update() override {
+    return &var_allowed_connection_types_for_update_;
+  }
+
+  Variable<std::string>* var_owner() override {
+    return &var_owner_;
+  }
+
+  Variable<bool>* var_http_downloads_enabled() override {
+    return &var_http_downloads_enabled_;
+  }
+
+  Variable<bool>* var_au_p2p_enabled() override {
+    return &var_au_p2p_enabled_;
+  }
+
+ private:
+  FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest);
+  FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded);
+  FRIEND_TEST(UmRealDevicePolicyProviderTest, ValuesUpdated);
+
+  // A static handler for the PropertyChangedCompleted signal from the session
+  // manager used as a callback.
+  static void HandlePropertyChangedCompletedStatic(DBusGProxy* proxy,
+                                                   const char* payload,
+                                                   void* data);
+
+  // Schedules a call to periodically refresh the device policy.
+  void RefreshDevicePolicyAndReschedule();
+
+  // Reloads the device policy and updates all the exposed variables.
+  void RefreshDevicePolicy();
+
+  // Updates the async variable |var| based on the result value of the method
+  // passed, which is a DevicePolicy getter method.
+  template<typename T>
+  void UpdateVariable(AsyncCopyVariable<T>* var,
+                      bool (policy::DevicePolicy::*getter_method)(T*) const);
+
+  // Updates the async variable |var| based on the result value of the getter
+  // method passed, which is a wrapper getter on this class.
+  template<typename T>
+  void UpdateVariable(
+      AsyncCopyVariable<T>* var,
+      bool (RealDevicePolicyProvider::*getter_method)(T*) const);
+
+  // Wrapper for DevicePolicy::GetScatterFactorInSeconds() that converts the
+  // result to a base::TimeDelta. It returns the same value as
+  // GetScatterFactorInSeconds().
+  bool ConvertScatterFactor(base::TimeDelta* scatter_factor) const;
+
+  // Wrapper for DevicePolicy::GetAllowedConnectionTypesForUpdate() that
+  // converts the result to a set of ConnectionType elements instead of strings.
+  bool ConvertAllowedConnectionTypesForUpdate(
+      std::set<ConnectionType>* allowed_types) const;
+
+  // Used for fetching information about the device policy.
+  policy::PolicyProvider* policy_provider_;
+
+  // Used to schedule refreshes of the device policy.
+  chromeos::MessageLoop::TaskId scheduled_refresh_ =
+      chromeos::MessageLoop::kTaskIdNull;
+
+  // The DBus interface (mockable) and a session manager proxy.
+  chromeos_update_engine::DBusWrapperInterface* const dbus_;
+  DBusGProxy* manager_proxy_ = nullptr;
+
+  // Variable exposing whether the policy is loaded.
+  AsyncCopyVariable<bool> var_device_policy_is_loaded_{
+      "policy_is_loaded", false};
+
+  // Variables mapping the exposed methods from the policy::DevicePolicy.
+  AsyncCopyVariable<std::string> var_release_channel_{"release_channel"};
+  AsyncCopyVariable<bool> var_release_channel_delegated_{
+      "release_channel_delegated"};
+  AsyncCopyVariable<bool> var_update_disabled_{"update_disabled"};
+  AsyncCopyVariable<std::string> var_target_version_prefix_{
+      "target_version_prefix"};
+  AsyncCopyVariable<base::TimeDelta> var_scatter_factor_{"scatter_factor"};
+  AsyncCopyVariable<std::set<ConnectionType>>
+      var_allowed_connection_types_for_update_{
+          "allowed_connection_types_for_update"};
+  AsyncCopyVariable<std::string> var_owner_{"owner"};
+  AsyncCopyVariable<bool> var_http_downloads_enabled_{"http_downloads_enabled"};
+  AsyncCopyVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled"};
+
+  DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/real_device_policy_provider_unittest.cc b/update_manager/real_device_policy_provider_unittest.cc
new file mode 100644
index 0000000..75d7c48
--- /dev/null
+++ b/update_manager/real_device_policy_provider_unittest.cc
@@ -0,0 +1,247 @@
+// Copyright (c) 2014 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/update_manager/real_device_policy_provider.h"
+
+#include <memory>
+
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+#include <policy/mock_device_policy.h>
+#include <policy/mock_libpolicy.h>
+
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using testing::DoAll;
+using testing::Mock;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace {
+
+// Fake dbus-glib objects. These should be different values, to ease diagnosis
+// of errors.
+DBusGConnection* const kFakeConnection = reinterpret_cast<DBusGConnection*>(1);
+DBusGProxy* const kFakeManagerProxy = reinterpret_cast<DBusGProxy*>(2);
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealDevicePolicyProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    provider_.reset(new RealDevicePolicyProvider(&mock_dbus_,
+                                                 &mock_policy_provider_));
+    // By default, we have a device policy loaded. Tests can call
+    // SetUpNonExistentDevicePolicy() to override this.
+    SetUpExistentDevicePolicy();
+
+    SetUpDBusSignalExpectations();
+  }
+
+  void TearDown() override {
+    // Check for leaked callbacks on the main loop.
+    EXPECT_EQ(0, MessageLoopRunMaxIterations(MessageLoop::current(), 100));
+
+    // We need to set these expectation before the object is destroyed but
+    // after it finished running the test so the values of signal_callback_ and
+    // signal_callback_data_ are correct.
+    EXPECT_CALL(mock_dbus_, ProxyDisconnectSignal(
+            kFakeManagerProxy,
+            StrEq(login_manager::kPropertyChangeCompleteSignal),
+            signal_callback_,
+            signal_callback_data_));
+    EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeManagerProxy));
+
+    provider_.reset();
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  void SetUpNonExistentDevicePolicy() {
+    ON_CALL(mock_policy_provider_, Reload())
+        .WillByDefault(Return(false));
+    ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+        .WillByDefault(Return(false));
+    EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+  }
+
+  void SetUpExistentDevicePolicy() {
+    // Setup the default behavior of the mocked PolicyProvider.
+    ON_CALL(mock_policy_provider_, Reload())
+        .WillByDefault(Return(true));
+    ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+        .WillByDefault(Return(true));
+    ON_CALL(mock_policy_provider_, GetDevicePolicy())
+        .WillByDefault(ReturnRef(mock_device_policy_));
+  }
+
+  void SetUpDBusSignalExpectations() {
+    // Setup the DBus connection with default actions that should be performed
+    // once.
+    EXPECT_CALL(mock_dbus_, BusGet(_, _)).WillOnce(
+        Return(kFakeConnection));
+    EXPECT_CALL(mock_dbus_, ProxyNewForName(
+            kFakeConnection, StrEq(login_manager::kSessionManagerServiceName),
+            StrEq(login_manager::kSessionManagerServicePath),
+            StrEq(login_manager::kSessionManagerInterface)))
+        .WillOnce(Return(kFakeManagerProxy));
+
+    // Expect the signal to be added, registered and released.
+    EXPECT_CALL(mock_dbus_, ProxyAddSignal_1(
+            kFakeManagerProxy,
+            StrEq(login_manager::kPropertyChangeCompleteSignal),
+            G_TYPE_STRING));
+    EXPECT_CALL(mock_dbus_, ProxyConnectSignal(
+            kFakeManagerProxy,
+            StrEq(login_manager::kPropertyChangeCompleteSignal),
+            _ /* callback */, _ /* data */, _ /* free function */))
+        .WillOnce(DoAll(SaveArg<2>(&signal_callback_),
+                        SaveArg<3>(&signal_callback_data_)));
+  }
+
+  chromeos::FakeMessageLoop loop_{nullptr};
+  chromeos_update_engine::MockDBusWrapper mock_dbus_;
+  testing::NiceMock<policy::MockDevicePolicy> mock_device_policy_;
+  testing::NiceMock<policy::MockPolicyProvider> mock_policy_provider_;
+  unique_ptr<RealDevicePolicyProvider> provider_;
+
+  // The registered signal handler for the signal.
+  GCallback signal_callback_ = nullptr;
+  void* signal_callback_data_ = nullptr;
+};
+
+TEST_F(UmRealDevicePolicyProviderTest, RefreshScheduledTest) {
+  // Check that the RefreshPolicy gets scheduled by checking the EventId.
+  EXPECT_TRUE(provider_->Init());
+  EXPECT_NE(MessageLoop::kTaskIdNull, provider_->scheduled_refresh_);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, FirstReload) {
+  // Checks that the policy is reloaded and the DevicePolicy is consulted.
+  EXPECT_CALL(mock_policy_provider_, Reload());
+  EXPECT_TRUE(provider_->Init());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded) {
+  // Checks that the policy is reloaded by RefreshDevicePolicy().
+  SetUpNonExistentDevicePolicy();
+  EXPECT_CALL(mock_policy_provider_, Reload()).Times(2);
+  EXPECT_TRUE(provider_->Init());
+  // Force the policy refresh.
+  provider_->RefreshDevicePolicy();
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, SessionManagerSignalForcesReload) {
+  // Checks that a signal from the SessionManager forces a reload.
+  SetUpNonExistentDevicePolicy();
+  EXPECT_CALL(mock_policy_provider_, Reload()).Times(2);
+  EXPECT_TRUE(provider_->Init());
+
+  ASSERT_NE(nullptr, signal_callback_);
+  // Convert the GCallback to a function pointer and call it. GCallback is just
+  // a void function pointer to ensure that the type of the passed callback is a
+  // pointer. We need to cast it back to the right function type before calling
+  // it.
+  typedef void (*StaticSignalHandler)(DBusGProxy*, const char*, void*);
+  StaticSignalHandler signal_handler = reinterpret_cast<StaticSignalHandler>(
+      signal_callback_);
+  (*signal_handler)(kFakeManagerProxy, "success", signal_callback_data_);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyEmptyVariables) {
+  SetUpNonExistentDevicePolicy();
+  EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+  EXPECT_TRUE(provider_->Init());
+
+  UmTestUtils::ExpectVariableHasValue(false,
+                                      provider_->var_device_policy_is_loaded());
+
+  UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel_delegated());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_update_disabled());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_target_version_prefix());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_allowed_connection_types_for_update());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_owner());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_http_downloads_enabled());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_au_p2p_enabled());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) {
+  SetUpNonExistentDevicePolicy();
+  EXPECT_TRUE(provider_->Init());
+  Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+
+  // Reload the policy with a good one and set some values as present. The
+  // remaining values are false.
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetReleaseChannel(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(string("mychannel")),
+                      Return(true)));
+  EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+      .WillOnce(Return(false));
+
+  provider_->RefreshDevicePolicy();
+
+  UmTestUtils::ExpectVariableHasValue(true,
+                                      provider_->var_device_policy_is_loaded());
+
+  // Test that at least one variable is set, to ensure the refresh occurred.
+  UmTestUtils::ExpectVariableHasValue(string("mychannel"),
+                                      provider_->var_release_channel());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_allowed_connection_types_for_update());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ScatterFactorConverted) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(1234), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+
+  UmTestUtils::ExpectVariableHasValue(TimeDelta::FromSeconds(1234),
+                                      provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NegativeScatterFactorIgnored) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(-1), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+
+  UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, AllowedTypesConverted) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+      .WillOnce(DoAll(SetArgumentPointee<0>(
+                          set<string>{"bluetooth", "wifi", "not-a-type"}),
+                      Return(true)));
+  EXPECT_TRUE(provider_->Init());
+
+  UmTestUtils::ExpectVariableHasValue(
+      set<ConnectionType>{ConnectionType::kWifi, ConnectionType::kBluetooth},
+      provider_->var_allowed_connection_types_for_update());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_random_provider.cc b/update_manager/real_random_provider.cc
new file mode 100644
index 0000000..2aa8d57
--- /dev/null
+++ b/update_manager/real_random_provider.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2014 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/update_manager/real_random_provider.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/scoped_file.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/update_manager/variable.h"
+
+using std::string;
+
+namespace {
+
+// The device providing randomness.
+const char* kRandomDevice = "/dev/urandom";
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+// A random seed variable.
+class RandomSeedVariable : public Variable<uint64_t> {
+ public:
+  // RandomSeedVariable is initialized as kVariableModeConst to let the
+  // EvaluationContext cache the value between different evaluations of the same
+  // policy request.
+  RandomSeedVariable(const string& name, FILE* fp)
+      : Variable<uint64_t>(name, kVariableModeConst), fp_(fp) {}
+  ~RandomSeedVariable() override {}
+
+ protected:
+  const uint64_t* GetValue(base::TimeDelta /* timeout */,
+                           string* errmsg) override {
+    uint64_t result;
+    // Aliasing via char pointer abides by the C/C++ strict-aliasing rules.
+    char* const buf = reinterpret_cast<char*>(&result);
+    unsigned int buf_rd = 0;
+
+    while (buf_rd < sizeof(result)) {
+      int rd = fread(buf + buf_rd, 1, sizeof(result) - buf_rd, fp_.get());
+      if (rd == 0 || ferror(fp_.get())) {
+        // Either EOF on fp or read failed.
+        if (errmsg) {
+          *errmsg = base::StringPrintf(
+              "Error reading from the random device: %s", kRandomDevice);
+        }
+        return nullptr;
+      }
+      buf_rd += rd;
+    }
+
+    return new uint64_t(result);
+  }
+
+ private:
+  base::ScopedFILE fp_;
+
+  DISALLOW_COPY_AND_ASSIGN(RandomSeedVariable);
+};
+
+bool RealRandomProvider::Init(void) {
+  FILE* fp = fopen(kRandomDevice, "r");
+  if (!fp)
+    return false;
+  var_seed_.reset(new RandomSeedVariable("seed", fp));
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_random_provider.h b/update_manager/real_random_provider.h
new file mode 100644
index 0000000..d159933
--- /dev/null
+++ b/update_manager/real_random_provider.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// RandomProvider implementation class.
+class RealRandomProvider : public RandomProvider {
+ public:
+  RealRandomProvider() {}
+
+  Variable<uint64_t>* var_seed() override { return var_seed_.get(); }
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+ private:
+  // The seed() scoped variable.
+  std::unique_ptr<Variable<uint64_t>> var_seed_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealRandomProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
diff --git a/update_manager/real_random_provider_unittest.cc b/update_manager/real_random_provider_unittest.cc
new file mode 100644
index 0000000..899ef6f
--- /dev/null
+++ b/update_manager/real_random_provider_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2014 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/update_manager/real_random_provider.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealRandomProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // The provider initializes correctly.
+    provider_.reset(new RealRandomProvider());
+    ASSERT_NE(nullptr, provider_.get());
+    ASSERT_TRUE(provider_->Init());
+
+    provider_->var_seed();
+  }
+
+  unique_ptr<RealRandomProvider> provider_;
+};
+
+TEST_F(UmRealRandomProviderTest, InitFinalize) {
+  // The provider initializes all variables with valid objects.
+  EXPECT_NE(nullptr, provider_->var_seed());
+}
+
+TEST_F(UmRealRandomProviderTest, GetRandomValues) {
+  // Should not return the same random seed repeatedly.
+  unique_ptr<const uint64_t> value(
+      provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+  ASSERT_NE(nullptr, value.get());
+
+  // Test that at least the returned values are different. This test fails,
+  // by design, once every 2^320 runs.
+  bool is_same_value = true;
+  for (int i = 0; i < 5; i++) {
+    unique_ptr<const uint64_t> other_value(
+        provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(),
+                                        nullptr));
+    ASSERT_NE(nullptr, other_value.get());
+    is_same_value = is_same_value && *other_value == *value;
+  }
+  EXPECT_FALSE(is_same_value);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_shill_provider.cc b/update_manager/real_shill_provider.cc
new file mode 100644
index 0000000..609fd01
--- /dev/null
+++ b/update_manager/real_shill_provider.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2014 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/update_manager/real_shill_provider.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "update_engine/glib_utils.h"
+
+using std::string;
+
+namespace {
+
+// Looks up a |key| in a GLib |hash_table| and returns the unboxed string from
+// the corresponding GValue, if found.
+const char* GetStrProperty(GHashTable* hash_table, const char* key) {
+  auto gval = reinterpret_cast<GValue*>(g_hash_table_lookup(hash_table, key));
+  return (gval ? g_value_get_string(gval) : nullptr);
+}
+
+};  // namespace
+
+
+namespace chromeos_update_manager {
+
+RealShillProvider::~RealShillProvider() {
+  // Detach signal handler, free manager proxy.
+  dbus_->ProxyDisconnectSignal(manager_proxy_, shill::kMonitorPropertyChanged,
+                               G_CALLBACK(HandlePropertyChangedStatic),
+                               this);
+  dbus_->ProxyUnref(manager_proxy_);
+}
+
+ConnectionType RealShillProvider::ParseConnectionType(const char* type_str) {
+  if (!strcmp(type_str, shill::kTypeEthernet))
+    return ConnectionType::kEthernet;
+  if (!strcmp(type_str, shill::kTypeWifi))
+    return ConnectionType::kWifi;
+  if (!strcmp(type_str, shill::kTypeWimax))
+    return ConnectionType::kWimax;
+  if (!strcmp(type_str, shill::kTypeBluetooth))
+    return ConnectionType::kBluetooth;
+  if (!strcmp(type_str, shill::kTypeCellular))
+    return ConnectionType::kCellular;
+
+  return ConnectionType::kUnknown;
+}
+
+ConnectionTethering RealShillProvider::ParseConnectionTethering(
+    const char* tethering_str) {
+  if (!strcmp(tethering_str, shill::kTetheringNotDetectedState))
+    return ConnectionTethering::kNotDetected;
+  if (!strcmp(tethering_str, shill::kTetheringSuspectedState))
+    return ConnectionTethering::kSuspected;
+  if (!strcmp(tethering_str, shill::kTetheringConfirmedState))
+    return ConnectionTethering::kConfirmed;
+
+  return ConnectionTethering::kUnknown;
+}
+
+bool RealShillProvider::Init() {
+  // Obtain a DBus connection.
+  GError* error = nullptr;
+  connection_ = dbus_->BusGet(DBUS_BUS_SYSTEM, &error);
+  if (!connection_) {
+    LOG(ERROR) << "Failed to initialize DBus connection: "
+               << chromeos_update_engine::utils::GetAndFreeGError(&error);
+    return false;
+  }
+
+  // Allocate a shill manager proxy.
+  manager_proxy_ = GetProxy(shill::kFlimflamServicePath,
+                            shill::kFlimflamManagerInterface);
+
+  // Subscribe to the manager's PropertyChanged signal.
+  dbus_->ProxyAddSignal_2(manager_proxy_, shill::kMonitorPropertyChanged,
+                          G_TYPE_STRING, G_TYPE_VALUE);
+  dbus_->ProxyConnectSignal(manager_proxy_, shill::kMonitorPropertyChanged,
+                            G_CALLBACK(HandlePropertyChangedStatic),
+                            this, nullptr);
+
+  // Attempt to read initial connection status. Even if this fails because shill
+  // is not responding (e.g. it is down) we'll be notified via "PropertyChanged"
+  // signal as soon as it comes up, so this is not a critical step.
+  GHashTable* hash_table = nullptr;
+  if (GetProperties(manager_proxy_, &hash_table)) {
+    GValue* value = reinterpret_cast<GValue*>(
+        g_hash_table_lookup(hash_table, shill::kDefaultServiceProperty));
+    ProcessDefaultService(value);
+    g_hash_table_unref(hash_table);
+  }
+
+  return true;
+}
+
+DBusGProxy* RealShillProvider::GetProxy(const char* path,
+                                        const char* interface) {
+  return dbus_->ProxyNewForName(connection_, shill::kFlimflamServiceName,
+                                path, interface);
+}
+
+bool RealShillProvider::GetProperties(DBusGProxy* proxy,
+                                      GHashTable** result_p) {
+  GError* error = nullptr;
+  if (!dbus_->ProxyCall_0_1(proxy, shill::kGetPropertiesFunction, &error,
+                            result_p)) {
+    LOG(ERROR) << "Calling shill via DBus proxy failed: "
+               << chromeos_update_engine::utils::GetAndFreeGError(&error);
+    return false;
+  }
+  return true;
+}
+
+bool RealShillProvider::ProcessDefaultService(GValue* value) {
+  // Decode the string from the boxed value.
+  const char* default_service_path_str = nullptr;
+  if (!(value && (default_service_path_str = g_value_get_string(value))))
+    return false;
+
+  // Anything changed?
+  if (default_service_path_ == default_service_path_str)
+    return true;
+
+  // Update the connection status.
+  default_service_path_ = default_service_path_str;
+  bool is_connected = (default_service_path_ != "/");
+  var_is_connected_.SetValue(is_connected);
+  var_conn_last_changed_.SetValue(clock_->GetWallclockTime());
+
+  // Update the connection attributes.
+  if (is_connected) {
+    DBusGProxy* service_proxy = GetProxy(default_service_path_.c_str(),
+                                         shill::kFlimflamServiceInterface);
+    GHashTable* hash_table = nullptr;
+    if (GetProperties(service_proxy, &hash_table)) {
+      // Get the connection type.
+      const char* type_str = GetStrProperty(hash_table, shill::kTypeProperty);
+      if (type_str && !strcmp(type_str, shill::kTypeVPN)) {
+        type_str = GetStrProperty(hash_table,
+                                  shill::kPhysicalTechnologyProperty);
+      }
+      if (type_str) {
+        var_conn_type_.SetValue(ParseConnectionType(type_str));
+      } else {
+        var_conn_type_.UnsetValue();
+        LOG(ERROR) << "Could not find connection type ("
+                   << default_service_path_ << ")";
+      }
+
+      // Get the connection tethering mode.
+      const char* tethering_str = GetStrProperty(hash_table,
+                                                 shill::kTetheringProperty);
+      if (tethering_str) {
+        var_conn_tethering_.SetValue(ParseConnectionTethering(tethering_str));
+      } else {
+        var_conn_tethering_.UnsetValue();
+        LOG(ERROR) << "Could not find connection tethering mode ("
+                   << default_service_path_ << ")";
+      }
+
+      g_hash_table_unref(hash_table);
+    }
+    dbus_->ProxyUnref(service_proxy);
+  } else {
+    var_conn_type_.UnsetValue();
+    var_conn_tethering_.UnsetValue();
+  }
+
+  return true;
+}
+
+void RealShillProvider::HandlePropertyChanged(DBusGProxy* proxy,
+                                              const char* name, GValue* value) {
+  if (!strcmp(name, shill::kDefaultServiceProperty))
+    ProcessDefaultService(value);
+}
+
+void RealShillProvider::HandlePropertyChangedStatic(DBusGProxy* proxy,
+                                                    const char* name,
+                                                    GValue* value,
+                                                    void* data) {
+  auto obj = reinterpret_cast<RealShillProvider*>(data);
+  obj->HandlePropertyChanged(proxy, name, value);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_shill_provider.h b/update_manager/real_shill_provider.h
new file mode 100644
index 0000000..5b50bf4
--- /dev/null
+++ b/update_manager/real_shill_provider.h
@@ -0,0 +1,97 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+
+// TODO(garnold) Much of the functionality in this module was adapted from the
+// update engine's connection_manager.  We need to make sure to deprecate use of
+// connection manager when the time comes.
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+using chromeos_update_engine::ClockInterface;
+using chromeos_update_engine::DBusWrapperInterface;
+
+namespace chromeos_update_manager {
+
+// ShillProvider concrete implementation.
+class RealShillProvider : public ShillProvider {
+ public:
+  RealShillProvider(DBusWrapperInterface* dbus, ClockInterface* clock)
+      : dbus_(dbus), clock_(clock) {}
+
+  ~RealShillProvider() override;
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_is_connected() override {
+    return &var_is_connected_;
+  }
+
+  Variable<ConnectionType>* var_conn_type() override {
+    return &var_conn_type_;
+  }
+
+  Variable<ConnectionTethering>* var_conn_tethering() override {
+    return &var_conn_tethering_;
+  }
+
+  Variable<base::Time>* var_conn_last_changed() override {
+    return &var_conn_last_changed_;
+  }
+
+  // Helper methods for converting shill strings into symbolic values.
+  static ConnectionType ParseConnectionType(const char* type_str);
+  static ConnectionTethering ParseConnectionTethering(
+      const char* tethering_str);
+
+ private:
+  // Return a DBus proxy for a given |path| and |interface| within shill.
+  DBusGProxy* GetProxy(const char* path, const char* interface);
+
+  // Issues a GetProperties call through a given |proxy|, storing the result to
+  // |*result_p|. Returns true on success.
+  bool GetProperties(DBusGProxy* proxy, GHashTable** result_p);
+
+  // Process a default connection value, update last change time as needed.
+  bool ProcessDefaultService(GValue* value);
+
+  // A handler for manager PropertyChanged signal, and a static version.
+  void HandlePropertyChanged(DBusGProxy* proxy, const char* name,
+                             GValue* value);
+  static void HandlePropertyChangedStatic(DBusGProxy* proxy, const char* name,
+                                          GValue* value, void* data);
+
+  // The current default service path, if connected.
+  std::string default_service_path_;
+
+  // The DBus interface (mockable), connection, and a shill manager proxy.
+  DBusWrapperInterface* const dbus_;
+  DBusGConnection* connection_ = nullptr;
+  DBusGProxy* manager_proxy_ = nullptr;
+
+  // A clock abstraction (mockable).
+  ClockInterface* const clock_;
+
+  // The provider's variables.
+  AsyncCopyVariable<bool> var_is_connected_{"is_connected"};
+  AsyncCopyVariable<ConnectionType> var_conn_type_{"conn_type"};
+  AsyncCopyVariable<ConnectionTethering> var_conn_tethering_{"conn_tethering"};
+  AsyncCopyVariable<base::Time> var_conn_last_changed_{"conn_last_changed"};
+
+  DISALLOW_COPY_AND_ASSIGN(RealShillProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
diff --git a/update_manager/real_shill_provider_unittest.cc b/update_manager/real_shill_provider_unittest.cc
new file mode 100644
index 0000000..78bdd93
--- /dev/null
+++ b/update_manager/real_shill_provider_unittest.cc
@@ -0,0 +1,542 @@
+// Copyright (c) 2014 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/update_manager/real_shill_provider.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/mock_dbus_wrapper.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::FakeClock;
+using chromeos_update_engine::MockDBusWrapper;
+using chromeos_update_engine::test_utils::GValueFree;
+using chromeos_update_engine::test_utils::GValueNewString;
+using std::pair;
+using std::unique_ptr;
+using testing::A;
+using testing::Eq;
+using testing::Mock;
+using testing::Return;
+using testing::SaveArg;
+using testing::SetArgPointee;
+using testing::StrEq;
+using testing::StrictMock;
+using testing::_;
+
+namespace {
+
+// Fake dbus-glib objects. These should be different values, to ease diagnosis
+// of errors.
+DBusGConnection* const kFakeConnection = reinterpret_cast<DBusGConnection*>(1);
+DBusGProxy* const kFakeManagerProxy = reinterpret_cast<DBusGProxy*>(2);
+DBusGProxy* const kFakeEthernetServiceProxy = reinterpret_cast<DBusGProxy*>(3);
+DBusGProxy* const kFakeWifiServiceProxy = reinterpret_cast<DBusGProxy*>(4);
+DBusGProxy* const kFakeWimaxServiceProxy = reinterpret_cast<DBusGProxy*>(5);
+DBusGProxy* const kFakeBluetoothServiceProxy = reinterpret_cast<DBusGProxy*>(6);
+DBusGProxy* const kFakeCellularServiceProxy = reinterpret_cast<DBusGProxy*>(7);
+DBusGProxy* const kFakeVpnServiceProxy = reinterpret_cast<DBusGProxy*>(8);
+DBusGProxy* const kFakeUnknownServiceProxy = reinterpret_cast<DBusGProxy*>(9);
+
+// Fake service paths.
+const char* const kFakeEthernetServicePath = "/fake-ethernet-service";
+const char* const kFakeWifiServicePath = "/fake-wifi-service";
+const char* const kFakeWimaxServicePath = "/fake-wimax-service";
+const char* const kFakeBluetoothServicePath = "/fake-bluetooth-service";
+const char* const kFakeCellularServicePath = "/fake-cellular-service";
+const char* const kFakeVpnServicePath = "/fake-vpn-service";
+const char* const kFakeUnknownServicePath = "/fake-unknown-service";
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealShillProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // By default, initialize the provider so that it gets an initial connection
+    // status from shill. This simulates the common case where shill is
+    // available and responding during RealShillProvider initialization.
+    Init(true);
+  }
+
+  void TearDown() override {
+    Shutdown();
+  }
+
+  // Initialize the RealShillProvider under test. If |do_init_conn_status| is
+  // true, configure mocks to respond to the initial connection status check
+  // with shill. Otherwise, the initial check will fail.
+  void Init(bool do_init_conn_status) {
+    // Properly shutdown a previously initialized provider.
+    if (provider_.get())
+      Shutdown();
+
+    provider_.reset(new RealShillProvider(&mock_dbus_, &fake_clock_));
+    ASSERT_NE(nullptr, provider_.get());
+    fake_clock_.SetWallclockTime(InitTime());
+
+    // A DBus connection should only be obtained once.
+    EXPECT_CALL(mock_dbus_, BusGet(_, _)).WillOnce(
+        Return(kFakeConnection));
+
+    // A manager proxy should only be obtained once.
+    EXPECT_CALL(mock_dbus_, ProxyNewForName(
+            kFakeConnection, StrEq(shill::kFlimflamServiceName),
+            StrEq(shill::kFlimflamServicePath),
+            StrEq(shill::kFlimflamManagerInterface)))
+        .WillOnce(Return(kFakeManagerProxy));
+
+    // The PropertyChanged signal should be subscribed to.
+    EXPECT_CALL(mock_dbus_, ProxyAddSignal_2(
+            kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
+            G_TYPE_STRING, G_TYPE_VALUE))
+        .WillOnce(Return());
+    EXPECT_CALL(mock_dbus_, ProxyConnectSignal(
+            kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
+            _, _, _))
+        .WillOnce(
+            DoAll(SaveArg<2>(reinterpret_cast<void (**)()>(&signal_handler_)),
+                  SaveArg<3>(&signal_data_),
+                  Return()));
+
+    // Mock a response to an initial connection check (optional).
+    GHashTable* manager_properties = nullptr;
+    if (do_init_conn_status) {
+      pair<const char*, const char*> manager_pairs[] = {
+        {shill::kDefaultServiceProperty, "/"},
+      };
+      manager_properties = SetupGetPropertiesOkay(
+          kFakeManagerProxy, arraysize(manager_pairs), manager_pairs);
+    } else {
+      SetupGetPropertiesFail(kFakeManagerProxy);
+    }
+
+    // Check that provider initializes correctly.
+    ASSERT_TRUE(provider_->Init());
+
+    // All mocked calls should have been exercised by now.
+    Mock::VerifyAndClear(&mock_dbus_);
+
+    // Release properties hash table (if provided).
+    if (manager_properties)
+      g_hash_table_unref(manager_properties);
+  }
+
+  // Deletes the RealShillProvider under test.
+  void Shutdown() {
+    // Make sure that DBus resources get freed.
+    EXPECT_CALL(mock_dbus_, ProxyDisconnectSignal(
+            kFakeManagerProxy, StrEq(shill::kMonitorPropertyChanged),
+            Eq(reinterpret_cast<void (*)()>(signal_handler_)),
+            Eq(signal_data_)))
+        .WillOnce(Return());
+    EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeManagerProxy)).WillOnce(Return());
+    provider_.reset();
+
+    // All mocked calls should have been exercised by now.
+    Mock::VerifyAndClear(&mock_dbus_);
+  }
+
+  // These methods generate fixed timestamps for use in faking the current time.
+  Time InitTime() {
+    Time::Exploded now_exp;
+    now_exp.year = 2014;
+    now_exp.month = 3;
+    now_exp.day_of_week = 2;
+    now_exp.day_of_month = 18;
+    now_exp.hour = 8;
+    now_exp.minute = 5;
+    now_exp.second = 33;
+    now_exp.millisecond = 675;
+    return Time::FromLocalExploded(now_exp);
+  }
+
+  Time ConnChangedTime() {
+    return InitTime() + TimeDelta::FromSeconds(10);
+  }
+
+  // Sets up a successful mock "GetProperties" call on |proxy|, writing a hash
+  // table containing |num_entries| entries formed by key/value pairs from
+  // |key_val_pairs| and returning true. Keys and values are plain C strings
+  // (const char*). The proxy call is expected to be made exactly once. Returns
+  // a pointer to a newly allocated hash table, which should be unreffed with
+  // g_hash_table_unref() when done.
+  GHashTable* SetupGetPropertiesOkay(
+      DBusGProxy* proxy, size_t num_entries,
+      pair<const char*, const char*>* key_val_pairs) {
+    // Allocate and populate the hash table.
+    GHashTable* properties = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                   free, GValueFree);
+    for (size_t i = 0; i < num_entries; i++) {
+      g_hash_table_insert(properties, strdup(key_val_pairs[i].first),
+                          GValueNewString(key_val_pairs[i].second));
+    }
+
+    // Set mock expectations.
+    EXPECT_CALL(mock_dbus_,
+                ProxyCall_0_1(proxy, StrEq(shill::kGetPropertiesFunction),
+                              _, A<GHashTable**>()))
+        .WillOnce(DoAll(SetArgPointee<3>(g_hash_table_ref(properties)),
+                        Return(true)));
+
+    return properties;
+  }
+
+  // Sets up a failing mock "GetProperties" call on |proxy|, returning false.
+  // The proxy call is expected to be made exactly once.
+  void SetupGetPropertiesFail(DBusGProxy* proxy) {
+    EXPECT_CALL(mock_dbus_,
+                ProxyCall_0_1(proxy, StrEq(shill::kGetPropertiesFunction),
+                              _, A<GHashTable**>()))
+      .WillOnce(Return(false));
+  }
+
+  // Sends a signal informing the provider about a default connection
+  // |service_path|. Returns the fake connection change time.
+  Time SendDefaultServiceSignal(const char* service_path) {
+    auto default_service_gval = GValueNewString(service_path);
+    const Time conn_change_time = ConnChangedTime();
+    fake_clock_.SetWallclockTime(conn_change_time);
+    signal_handler_(kFakeManagerProxy, shill::kDefaultServiceProperty,
+                    default_service_gval, signal_data_);
+    fake_clock_.SetWallclockTime(conn_change_time + TimeDelta::FromSeconds(5));
+    GValueFree(default_service_gval);
+    return conn_change_time;
+  }
+
+  // Sets up expectations for detection of a connection |service_path| with type
+  // |shill_type_str| and tethering mode |shill_tethering_str|. Ensures that the
+  // new connection status and change time are properly detected by the
+  // provider. Writes the fake connection change time to |conn_change_time_p|,
+  // if provided.
+  void SetupConnectionAndAttrs(const char* service_path,
+                               DBusGProxy* service_proxy,
+                               const char* shill_type_str,
+                               const char* shill_tethering_str,
+                               Time* conn_change_time_p) {
+    // Mock logic for querying the default service attributes.
+    EXPECT_CALL(mock_dbus_,
+                ProxyNewForName(
+                    kFakeConnection, StrEq(shill::kFlimflamServiceName),
+                    StrEq(service_path),
+                    StrEq(shill::kFlimflamServiceInterface)))
+        .WillOnce(Return(service_proxy));
+    EXPECT_CALL(mock_dbus_, ProxyUnref(service_proxy)).WillOnce(Return());
+    pair<const char*, const char*> service_pairs[] = {
+      {shill::kTypeProperty, shill_type_str},
+      {shill::kTetheringProperty, shill_tethering_str},
+    };
+    auto service_properties = SetupGetPropertiesOkay(
+        service_proxy, arraysize(service_pairs), service_pairs);
+
+    // Send a signal about a new default service.
+    auto conn_change_time = SendDefaultServiceSignal(service_path);
+
+    // Release the service properties hash tables.
+    g_hash_table_unref(service_properties);
+
+    // Query the connection status, ensure last change time reported correctly.
+    UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+    UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                        provider_->var_conn_last_changed());
+
+    // Write the connection change time to the output argument.
+    if (conn_change_time_p)
+      *conn_change_time_p = conn_change_time;
+  }
+
+  // Sets up a connection and tests that its type is being properly detected by
+  // the provider.
+  void SetupConnectionAndTestType(const char* service_path,
+                                  DBusGProxy* service_proxy,
+                                  const char* shill_type_str,
+                                  ConnectionType expected_conn_type) {
+    // Set up and test the connection, record the change time.
+    Time conn_change_time;
+    SetupConnectionAndAttrs(service_path, service_proxy, shill_type_str,
+                            shill::kTetheringNotDetectedState,
+                            &conn_change_time);
+
+    // Query the connection type, ensure last change time did not change.
+    UmTestUtils::ExpectVariableHasValue(expected_conn_type,
+                                        provider_->var_conn_type());
+    UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                        provider_->var_conn_last_changed());
+  }
+
+  // Sets up a connection and tests that its tethering mode is being properly
+  // detected by the provider.
+  void SetupConnectionAndTestTethering(
+      const char* service_path, DBusGProxy* service_proxy,
+      const char* shill_tethering_str,
+      ConnectionTethering expected_conn_tethering) {
+    // Set up and test the connection, record the change time.
+    Time conn_change_time;
+    SetupConnectionAndAttrs(service_path, service_proxy, shill::kTypeEthernet,
+                            shill_tethering_str, &conn_change_time);
+
+    // Query the connection tethering, ensure last change time did not change.
+    UmTestUtils::ExpectVariableHasValue(expected_conn_tethering,
+                                        provider_->var_conn_tethering());
+    UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                        provider_->var_conn_last_changed());
+  }
+
+  StrictMock<MockDBusWrapper> mock_dbus_;
+  FakeClock fake_clock_;
+  unique_ptr<RealShillProvider> provider_;
+  void (*signal_handler_)(DBusGProxy*, const char*, GValue*, void*);
+  void* signal_data_;
+};
+
+// Query the connection status, type and time last changed, as they were set
+// during initialization (no signals).
+TEST_F(UmRealShillProviderTest, ReadBaseValues) {
+  // Query the provider variables.
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+  UmTestUtils::ExpectVariableHasValue(InitTime(),
+                                      provider_->var_conn_last_changed());
+}
+
+// Test that Ethernet connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeEthernet) {
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             kFakeEthernetServiceProxy,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+}
+
+// Test that Wifi connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeWifi) {
+  SetupConnectionAndTestType(kFakeWifiServicePath,
+                             kFakeWifiServiceProxy,
+                             shill::kTypeWifi,
+                             ConnectionType::kWifi);
+}
+
+// Test that Wimax connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeWimax) {
+  SetupConnectionAndTestType(kFakeWimaxServicePath,
+                             kFakeWimaxServiceProxy,
+                             shill::kTypeWimax,
+                             ConnectionType::kWimax);
+}
+
+// Test that Bluetooth connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeBluetooth) {
+  SetupConnectionAndTestType(kFakeBluetoothServicePath,
+                             kFakeBluetoothServiceProxy,
+                             shill::kTypeBluetooth,
+                             ConnectionType::kBluetooth);
+}
+
+// Test that Cellular connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeCellular) {
+  SetupConnectionAndTestType(kFakeCellularServicePath,
+                             kFakeCellularServiceProxy,
+                             shill::kTypeCellular,
+                             ConnectionType::kCellular);
+}
+
+// Test that an unknown connection is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTypeUnknown) {
+  SetupConnectionAndTestType(kFakeUnknownServicePath,
+                             kFakeUnknownServiceProxy,
+                             "FooConnectionType",
+                             ConnectionType::kUnknown);
+}
+
+// Tests that VPN connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeVpn) {
+  // Mock logic for returning a default service path and its type.
+  EXPECT_CALL(mock_dbus_, ProxyNewForName(
+          kFakeConnection, StrEq(shill::kFlimflamServiceName),
+          StrEq(kFakeVpnServicePath), StrEq(shill::kFlimflamServiceInterface)))
+      .WillOnce(Return(kFakeVpnServiceProxy));
+  EXPECT_CALL(mock_dbus_, ProxyUnref(kFakeVpnServiceProxy)).WillOnce(Return());
+  pair<const char*, const char*> service_pairs[] = {
+    {shill::kTypeProperty, shill::kTypeVPN},
+    {shill::kPhysicalTechnologyProperty, shill::kTypeWifi},
+  };
+  auto service_properties = SetupGetPropertiesOkay(kFakeVpnServiceProxy,
+                                                   arraysize(service_pairs),
+                                                   service_pairs);
+
+  // Send a signal about a new default service.
+  Time conn_change_time = SendDefaultServiceSignal(kFakeVpnServicePath);
+
+  // Query the connection type, ensure last change time reported correctly.
+  UmTestUtils::ExpectVariableHasValue(ConnectionType::kWifi,
+                                      provider_->var_conn_type());
+  UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                      provider_->var_conn_last_changed());
+
+  // Release properties hash tables.
+  g_hash_table_unref(service_properties);
+}
+
+// Ensure that the connection type is properly cached in the provider through
+// subsequent variable readings.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheUsed) {
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             kFakeEthernetServiceProxy,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+                                      provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type remains valid even when a default
+// connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheRemainsValid) {
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             kFakeEthernetServiceProxy,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+
+  SendDefaultServiceSignal(kFakeEthernetServicePath);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+                                      provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type is invalidated and re-read when the
+// default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheInvalidated) {
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             kFakeEthernetServiceProxy,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+
+  SetupConnectionAndTestType(kFakeWifiServicePath,
+                             kFakeWifiServiceProxy,
+                             shill::kTypeWifi,
+                             ConnectionType::kWifi);
+}
+
+// Test that a non-tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringNotDetected) {
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  kFakeWifiServiceProxy,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+}
+
+// Test that a suspected tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringSuspected) {
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  kFakeWifiServiceProxy,
+                                  shill::kTetheringSuspectedState,
+                                  ConnectionTethering::kSuspected);
+}
+
+// Test that a confirmed tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringConfirmed) {
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  kFakeWifiServiceProxy,
+                                  shill::kTetheringConfirmedState,
+                                  ConnectionTethering::kConfirmed);
+}
+
+// Test that an unknown tethering mode is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringUnknown) {
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  kFakeWifiServiceProxy,
+                                  "FooConnTethering",
+                                  ConnectionTethering::kUnknown);
+}
+
+// Ensure that the connection tethering mode is properly cached in the provider.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheUsed) {
+  SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+                                  kFakeEthernetServiceProxy,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+                                      provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode remains valid even when a
+// default connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheRemainsValid) {
+  SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+                                  kFakeEthernetServiceProxy,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+
+  SendDefaultServiceSignal(kFakeEthernetServicePath);
+
+  UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+                                      provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode is invalidated and re-read
+// when the default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheInvalidated) {
+  SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+                                  kFakeEthernetServiceProxy,
+                                  shill::kTetheringNotDetectedState,
+                                  ConnectionTethering::kNotDetected);
+
+  SetupConnectionAndTestTethering(kFakeWifiServicePath,
+                                  kFakeWifiServiceProxy,
+                                  shill::kTetheringConfirmedState,
+                                  ConnectionTethering::kConfirmed);
+}
+
+// Fake two DBus signals prompting a default connection change, but otherwise
+// give the same service path. Check connection status and the time it was last
+// changed, making sure that it is the time when the first signal was sent (and
+// not the second).
+TEST_F(UmRealShillProviderTest, ReadLastChangedTimeTwoSignals) {
+  // Send a default service signal twice, advancing the clock in between.
+  Time conn_change_time;
+  SetupConnectionAndAttrs(kFakeEthernetServicePath, kFakeEthernetServiceProxy,
+                          shill::kTypeEthernet,
+                          shill::kTetheringNotDetectedState, &conn_change_time);
+  SendDefaultServiceSignal(kFakeEthernetServicePath);
+
+  // Query the connection status, ensure last change time reported correctly.
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+  UmTestUtils::ExpectVariableHasValue(conn_change_time,
+                                      provider_->var_conn_last_changed());
+}
+
+// Make sure that the provider initializes correctly even if shill is not
+// responding, that variables can be obtained, and that they all return a null
+// value (indicating that the underlying values were not set).
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadBaseValues) {
+  // Re-initialize the provider, no initial connection status response.
+  Init(false);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_is_connected());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_conn_last_changed());
+}
+
+// Test that, once a signal is received, the connection status and other info
+// can be read correctly.
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadConnTypeEthernet) {
+  // Re-initialize the provider, no initial connection status response.
+  Init(false);
+  SetupConnectionAndTestType(kFakeEthernetServicePath,
+                             kFakeEthernetServiceProxy,
+                             shill::kTypeEthernet,
+                             ConnectionType::kEthernet);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_state.h b/update_manager/real_state.h
new file mode 100644
index 0000000..ac70648
--- /dev/null
+++ b/update_manager/real_state.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_REAL_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// State concrete implementation.
+class RealState : public State {
+ public:
+  ~RealState() override {}
+
+  RealState(ConfigProvider* config_provider,
+            DevicePolicyProvider* device_policy_provider,
+            RandomProvider* random_provider,
+            ShillProvider* shill_provider,
+            SystemProvider* system_provider,
+            TimeProvider* time_provider,
+            UpdaterProvider* updater_provider) :
+      config_provider_(config_provider),
+      device_policy_provider_(device_policy_provider),
+      random_provider_(random_provider),
+      shill_provider_(shill_provider),
+      system_provider_(system_provider),
+      time_provider_(time_provider),
+      updater_provider_(updater_provider) {}
+
+  // These methods return the given provider.
+  ConfigProvider* config_provider() override {
+    return config_provider_.get();
+  }
+  DevicePolicyProvider* device_policy_provider() override {
+    return device_policy_provider_.get();
+  }
+  RandomProvider* random_provider() override {
+    return random_provider_.get();
+  }
+  ShillProvider* shill_provider() override {
+    return shill_provider_.get();
+  }
+  SystemProvider* system_provider() override {
+    return system_provider_.get();
+  }
+  TimeProvider* time_provider() override {
+    return time_provider_.get();
+  }
+  UpdaterProvider* updater_provider() override {
+    return updater_provider_.get();
+  }
+
+ private:
+  // Instances of the providers.
+  std::unique_ptr<ConfigProvider> config_provider_;
+  std::unique_ptr<DevicePolicyProvider> device_policy_provider_;
+  std::unique_ptr<RandomProvider> random_provider_;
+  std::unique_ptr<ShillProvider> shill_provider_;
+  std::unique_ptr<SystemProvider> system_provider_;
+  std::unique_ptr<TimeProvider> time_provider_;
+  std::unique_ptr<UpdaterProvider> updater_provider_;
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
diff --git a/update_manager/real_system_provider.cc b/update_manager/real_system_provider.cc
new file mode 100644
index 0000000..1410106
--- /dev/null
+++ b/update_manager/real_system_provider.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2014 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/update_manager/real_system_provider.h"
+
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <vboot/crossystem.h>
+
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/utils.h"
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+bool RealSystemProvider::Init() {
+  var_is_normal_boot_mode_.reset(
+      new ConstCopyVariable<bool>("is_normal_boot_mode",
+                                  VbGetSystemPropertyInt("devsw_boot") != 0));
+
+  var_is_official_build_.reset(
+      new ConstCopyVariable<bool>("is_official_build",
+                                  VbGetSystemPropertyInt("debug_build") == 0));
+
+  var_is_oobe_complete_.reset(
+      new CallCopyVariable<bool>(
+          "is_oobe_complete",
+          base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete,
+                     base::Unretained(hardware_), nullptr)));
+
+  var_is_boot_device_removable_.reset(
+      new ConstCopyVariable<bool>("is_boot_device_removable",
+                                  hardware_->IsBootDeviceRemovable()));
+
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_system_provider.h b/update_manager/real_system_provider.h
new file mode 100644
index 0000000..9081d94
--- /dev/null
+++ b/update_manager/real_system_provider.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/hardware_interface.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_manager {
+
+// SystemProvider concrete implementation.
+class RealSystemProvider : public SystemProvider {
+ public:
+  explicit RealSystemProvider(
+      chromeos_update_engine::HardwareInterface* hardware)
+      : hardware_(hardware) {}
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<bool>* var_is_normal_boot_mode() override {
+    return var_is_normal_boot_mode_.get();
+  }
+
+  Variable<bool>* var_is_official_build() override {
+    return var_is_official_build_.get();
+  }
+
+  Variable<bool>* var_is_oobe_complete() override {
+    return var_is_oobe_complete_.get();
+  }
+
+  Variable<bool>* var_is_boot_device_removable() override {
+    return var_is_boot_device_removable_.get();
+  }
+
+ private:
+  std::unique_ptr<Variable<bool>> var_is_normal_boot_mode_;
+  std::unique_ptr<Variable<bool>> var_is_official_build_;
+  std::unique_ptr<Variable<bool>> var_is_oobe_complete_;
+  std::unique_ptr<Variable<bool>> var_is_boot_device_removable_;
+
+  chromeos_update_engine::HardwareInterface* hardware_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealSystemProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
diff --git a/update_manager/real_system_provider_unittest.cc b/update_manager/real_system_provider_unittest.cc
new file mode 100644
index 0000000..41222d9
--- /dev/null
+++ b/update_manager/real_system_provider_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2014 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/update_manager/real_system_provider.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_hardware.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealSystemProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    provider_.reset(new RealSystemProvider(&fake_hardware_));
+    EXPECT_TRUE(provider_->Init());
+  }
+
+  chromeos_update_engine::FakeHardware fake_hardware_;
+  unique_ptr<RealSystemProvider> provider_;
+};
+
+TEST_F(UmRealSystemProviderTest, InitTest) {
+  EXPECT_NE(nullptr, provider_->var_is_normal_boot_mode());
+  EXPECT_NE(nullptr, provider_->var_is_official_build());
+  EXPECT_NE(nullptr, provider_->var_is_oobe_complete());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteTrue) {
+  fake_hardware_.SetIsOOBEComplete(base::Time());
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_complete());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteFalse) {
+  fake_hardware_.UnsetIsOOBEComplete();
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_complete());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_time_provider.cc b/update_manager/real_time_provider.cc
new file mode 100644
index 0000000..dab9829
--- /dev/null
+++ b/update_manager/real_time_provider.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2014 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/update_manager/real_time_provider.h"
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A variable returning the current date.
+class CurrDateVariable : public Variable<Time> {
+ public:
+  // TODO(garnold) Turn this into an async variable with the needed callback
+  // logic for when it value changes.
+  CurrDateVariable(const string& name, ClockInterface* clock)
+      : Variable<Time>(name, TimeDelta::FromHours(1)), clock_(clock) {}
+
+ protected:
+  virtual const Time* GetValue(TimeDelta /* timeout */,
+                               string* /* errmsg */) {
+    Time::Exploded now_exp;
+    clock_->GetWallclockTime().LocalExplode(&now_exp);
+    now_exp.hour = now_exp.minute = now_exp.second = now_exp.millisecond = 0;
+    return new Time(Time::FromLocalExploded(now_exp));
+  }
+
+ private:
+  ClockInterface* clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(CurrDateVariable);
+};
+
+// A variable returning the current hour in local time.
+class CurrHourVariable : public Variable<int> {
+ public:
+  // TODO(garnold) Turn this into an async variable with the needed callback
+  // logic for when it value changes.
+  CurrHourVariable(const string& name, ClockInterface* clock)
+      : Variable<int>(name, TimeDelta::FromMinutes(5)), clock_(clock) {}
+
+ protected:
+  virtual const int* GetValue(TimeDelta /* timeout */,
+                              string* /* errmsg */) {
+    Time::Exploded exploded;
+    clock_->GetWallclockTime().LocalExplode(&exploded);
+    return new int(exploded.hour);
+  }
+
+ private:
+  ClockInterface* clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(CurrHourVariable);
+};
+
+bool RealTimeProvider::Init() {
+  var_curr_date_.reset(new CurrDateVariable("curr_date", clock_));
+  var_curr_hour_.reset(new CurrHourVariable("curr_hour", clock_));
+  return true;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_time_provider.h b/update_manager/real_time_provider.h
new file mode 100644
index 0000000..63fc116
--- /dev/null
+++ b/update_manager/real_time_provider.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// TimeProvider concrete implementation.
+class RealTimeProvider : public TimeProvider {
+ public:
+  explicit RealTimeProvider(chromeos_update_engine::ClockInterface* clock)
+      : clock_(clock) {}
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init();
+
+  Variable<base::Time>* var_curr_date() override {
+    return var_curr_date_.get();
+  }
+
+  Variable<int>* var_curr_hour() override {
+    return var_curr_hour_.get();
+  }
+
+ private:
+  // A clock abstraction (fakeable).
+  chromeos_update_engine::ClockInterface* const clock_;
+
+  std::unique_ptr<Variable<base::Time>> var_curr_date_;
+  std::unique_ptr<Variable<int>> var_curr_hour_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealTimeProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
diff --git a/update_manager/real_time_provider_unittest.cc b/update_manager/real_time_provider_unittest.cc
new file mode 100644
index 0000000..90bd3c1
--- /dev/null
+++ b/update_manager/real_time_provider_unittest.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2014 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/update_manager/real_time_provider.h"
+
+#include <memory>
+
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using chromeos_update_engine::FakeClock;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealTimeProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // The provider initializes correctly.
+    provider_.reset(new RealTimeProvider(&fake_clock_));
+    ASSERT_NE(nullptr, provider_.get());
+    ASSERT_TRUE(provider_->Init());
+  }
+
+  // Generates a fixed timestamp for use in faking the current time.
+  Time CurrTime() {
+    Time::Exploded now_exp;
+    now_exp.year = 2014;
+    now_exp.month = 3;
+    now_exp.day_of_week = 2;
+    now_exp.day_of_month = 18;
+    now_exp.hour = 8;
+    now_exp.minute = 5;
+    now_exp.second = 33;
+    now_exp.millisecond = 675;
+    return Time::FromLocalExploded(now_exp);
+  }
+
+  FakeClock fake_clock_;
+  unique_ptr<RealTimeProvider> provider_;
+};
+
+TEST_F(UmRealTimeProviderTest, CurrDateValid) {
+  const Time now = CurrTime();
+  Time::Exploded exploded;
+  now.LocalExplode(&exploded);
+  exploded.hour = 0;
+  exploded.minute = 0;
+  exploded.second = 0;
+  exploded.millisecond = 0;
+  const Time expected = Time::FromLocalExploded(exploded);
+
+  fake_clock_.SetWallclockTime(now);
+  UmTestUtils::ExpectVariableHasValue(expected, provider_->var_curr_date());
+}
+
+TEST_F(UmRealTimeProviderTest, CurrHourValid) {
+  const Time now = CurrTime();
+  Time::Exploded expected;
+  now.LocalExplode(&expected);
+  fake_clock_.SetWallclockTime(now);
+  UmTestUtils::ExpectVariableHasValue(expected.hour,
+                                      provider_->var_curr_hour());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.cc b/update_manager/real_updater_provider.cc
new file mode 100644
index 0000000..ac70746
--- /dev/null
+++ b/update_manager/real_updater_provider.cc
@@ -0,0 +1,424 @@
+// Copyright (c) 2014 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/update_manager/real_updater_provider.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs.h"
+#include "update_engine/update_attempter.h"
+
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::OmahaRequestParams;
+using chromeos_update_engine::SystemState;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A templated base class for all update related variables. Provides uniform
+// construction and a system state handle.
+template<typename T>
+class UpdaterVariableBase : public Variable<T> {
+ public:
+  UpdaterVariableBase(const string& name, VariableMode mode,
+                      SystemState* system_state)
+      : Variable<T>(name, mode), system_state_(system_state) {}
+
+ protected:
+  // The system state used for pulling information from the updater.
+  inline SystemState* system_state() const { return system_state_; }
+
+ private:
+  SystemState* const system_state_;
+};
+
+// Helper class for issuing a GetStatus() to the UpdateAttempter.
+class GetStatusHelper {
+ public:
+  GetStatusHelper(SystemState* system_state, string* errmsg) {
+    is_success_ = system_state->update_attempter()->GetStatus(
+        &last_checked_time_, &progress_, &update_status_, &new_version_,
+        &payload_size_);
+    if (!is_success_ && errmsg)
+      *errmsg = "Failed to get a status update from the update engine";
+  }
+
+  inline bool is_success() { return is_success_; }
+  inline int64_t last_checked_time() { return last_checked_time_; }
+  inline double progress() { return progress_; }
+  inline const string& update_status() { return update_status_; }
+  inline const string& new_version() { return new_version_; }
+  inline int64_t payload_size() { return payload_size_; }
+
+ private:
+  bool is_success_;
+  int64_t last_checked_time_;
+  double progress_;
+  string update_status_;
+  string new_version_;
+  int64_t payload_size_;
+};
+
+// A variable reporting the time when a last update check was issued.
+class LastCheckedTimeVariable : public UpdaterVariableBase<Time> {
+ public:
+  LastCheckedTimeVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    return new Time(Time::FromTimeT(raw.last_checked_time()));
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(LastCheckedTimeVariable);
+};
+
+// A variable reporting the update (download) progress as a decimal fraction
+// between 0.0 and 1.0.
+class ProgressVariable : public UpdaterVariableBase<double> {
+ public:
+  ProgressVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<double>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const double* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    if (raw.progress() < 0.0 || raw.progress() > 1.0) {
+      if (errmsg) {
+        *errmsg = StringPrintf("Invalid progress value received: %f",
+                               raw.progress());
+      }
+      return nullptr;
+    }
+
+    return new double(raw.progress());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ProgressVariable);
+};
+
+// A variable reporting the stage in which the update process is.
+class StageVariable : public UpdaterVariableBase<Stage> {
+ public:
+  StageVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<Stage>(name, kVariableModePoll, system_state) {}
+
+ private:
+  struct CurrOpStrToStage {
+    const char* str;
+    Stage stage;
+  };
+  static const CurrOpStrToStage curr_op_str_to_stage[];
+
+  // Note: the method is defined outside the class so arraysize can work.
+  const Stage* GetValue(TimeDelta /* timeout */, string* errmsg) override;
+
+  DISALLOW_COPY_AND_ASSIGN(StageVariable);
+};
+
+const StageVariable::CurrOpStrToStage StageVariable::curr_op_str_to_stage[] = {
+  {update_engine::kUpdateStatusIdle, Stage::kIdle},
+  {update_engine::kUpdateStatusCheckingForUpdate, Stage::kCheckingForUpdate},
+  {update_engine::kUpdateStatusUpdateAvailable, Stage::kUpdateAvailable},
+  {update_engine::kUpdateStatusDownloading, Stage::kDownloading},
+  {update_engine::kUpdateStatusVerifying, Stage::kVerifying},
+  {update_engine::kUpdateStatusFinalizing, Stage::kFinalizing},
+  {update_engine::kUpdateStatusUpdatedNeedReboot, Stage::kUpdatedNeedReboot},
+  {  // NOLINT(whitespace/braces)
+    update_engine::kUpdateStatusReportingErrorEvent,
+    Stage::kReportingErrorEvent
+  },
+  {update_engine::kUpdateStatusAttemptingRollback, Stage::kAttemptingRollback},
+};
+
+const Stage* StageVariable::GetValue(TimeDelta /* timeout */,
+                                     string* errmsg) {
+  GetStatusHelper raw(system_state(), errmsg);
+  if (!raw.is_success())
+    return nullptr;
+
+  for (auto& key_val : curr_op_str_to_stage)
+    if (raw.update_status() == key_val.str)
+      return new Stage(key_val.stage);
+
+  if (errmsg)
+    *errmsg = string("Unknown update status: ") + raw.update_status();
+  return nullptr;
+}
+
+// A variable reporting the version number that an update is updating to.
+class NewVersionVariable : public UpdaterVariableBase<string> {
+ public:
+  NewVersionVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    return new string(raw.new_version());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(NewVersionVariable);
+};
+
+// A variable reporting the size of the update being processed in bytes.
+class PayloadSizeVariable : public UpdaterVariableBase<int64_t> {
+ public:
+  PayloadSizeVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<int64_t>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const int64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    GetStatusHelper raw(system_state(), errmsg);
+    if (!raw.is_success())
+      return nullptr;
+
+    if (raw.payload_size() < 0) {
+      if (errmsg)
+        *errmsg = string("Invalid payload size: %" PRId64, raw.payload_size());
+      return nullptr;
+    }
+
+    return new int64_t(raw.payload_size());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(PayloadSizeVariable);
+};
+
+// A variable reporting the point in time an update last completed in the
+// current boot cycle.
+//
+// TODO(garnold) In general, both the current boottime and wallclock time
+// readings should come from the time provider and be moderated by the
+// evaluation context, so that they are uniform throughout the evaluation of a
+// policy request.
+class UpdateCompletedTimeVariable : public UpdaterVariableBase<Time> {
+ public:
+  UpdateCompletedTimeVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    Time update_boottime;
+    if (!system_state()->update_attempter()->GetBootTimeAtUpdate(
+            &update_boottime)) {
+      if (errmsg)
+        *errmsg = "Update completed time could not be read";
+      return nullptr;
+    }
+
+    chromeos_update_engine::ClockInterface* clock = system_state()->clock();
+    Time curr_boottime = clock->GetBootTime();
+    if (curr_boottime < update_boottime) {
+      if (errmsg)
+        *errmsg = "Update completed time more recent than current time";
+      return nullptr;
+    }
+    TimeDelta duration_since_update = curr_boottime - update_boottime;
+    return new Time(clock->GetWallclockTime() - duration_since_update);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateCompletedTimeVariable);
+};
+
+// Variables reporting the current image channel.
+class CurrChannelVariable : public UpdaterVariableBase<string> {
+ public:
+  CurrChannelVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    OmahaRequestParams* request_params = system_state()->request_params();
+    string channel = request_params->current_channel();
+    if (channel.empty()) {
+      if (errmsg)
+        *errmsg = "No current channel";
+      return nullptr;
+    }
+    return new string(channel);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(CurrChannelVariable);
+};
+
+// Variables reporting the new image channel.
+class NewChannelVariable : public UpdaterVariableBase<string> {
+ public:
+  NewChannelVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<string>(name, kVariableModePoll, system_state) {}
+
+ private:
+  const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    OmahaRequestParams* request_params = system_state()->request_params();
+    string channel = request_params->target_channel();
+    if (channel.empty()) {
+      if (errmsg)
+        *errmsg = "No new channel";
+      return nullptr;
+    }
+    return new string(channel);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(NewChannelVariable);
+};
+
+// A variable class for reading Boolean prefs values.
+class BooleanPrefVariable : public UpdaterVariableBase<bool> {
+ public:
+  BooleanPrefVariable(const string& name, SystemState* system_state,
+                      const char* key, bool default_val)
+      : UpdaterVariableBase<bool>(name, kVariableModePoll, system_state),
+        key_(key), default_val_(default_val) {}
+
+ private:
+  const bool* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+    bool result = default_val_;
+    chromeos_update_engine::PrefsInterface* prefs = system_state()->prefs();
+    if (prefs && prefs->Exists(key_) && !prefs->GetBoolean(key_, &result)) {
+      if (errmsg)
+        *errmsg = string("Could not read boolean pref ") + key_;
+      return nullptr;
+    }
+    return new bool(result);
+  }
+
+  // The Boolean preference key and default value.
+  const char* const key_;
+  const bool default_val_;
+
+  DISALLOW_COPY_AND_ASSIGN(BooleanPrefVariable);
+};
+
+// A variable returning the number of consecutive failed update checks.
+class ConsecutiveFailedUpdateChecksVariable
+    : public UpdaterVariableBase<unsigned int> {
+ public:
+  ConsecutiveFailedUpdateChecksVariable(const string& name,
+                                        SystemState* system_state)
+      : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+                                          system_state) {}
+
+ private:
+  const unsigned int* GetValue(TimeDelta /* timeout */,
+                               string* /* errmsg */) override {
+    return new unsigned int(
+        system_state()->update_attempter()->consecutive_failed_update_checks());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ConsecutiveFailedUpdateChecksVariable);
+};
+
+// A variable returning the server-dictated poll interval.
+class ServerDictatedPollIntervalVariable
+    : public UpdaterVariableBase<unsigned int> {
+ public:
+  ServerDictatedPollIntervalVariable(const string& name,
+                                     SystemState* system_state)
+      : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+                                          system_state) {}
+
+ private:
+  const unsigned int* GetValue(TimeDelta /* timeout */,
+                               string* /* errmsg */) override {
+    return new unsigned int(
+        system_state()->update_attempter()->server_dictated_poll_interval());
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(ServerDictatedPollIntervalVariable);
+};
+
+// An async variable that tracks changes to forced update requests.
+class ForcedUpdateRequestedVariable
+    : public UpdaterVariableBase<UpdateRequestStatus> {
+ public:
+  ForcedUpdateRequestedVariable(const string& name, SystemState* system_state)
+      : UpdaterVariableBase<UpdateRequestStatus>::UpdaterVariableBase(
+          name, kVariableModeAsync, system_state) {
+    system_state->update_attempter()->set_forced_update_pending_callback(
+        new base::Callback<void(bool, bool)>(  // NOLINT(readability/function)
+            base::Bind(&ForcedUpdateRequestedVariable::Reset,
+                       base::Unretained(this))));
+  }
+
+ private:
+  const UpdateRequestStatus* GetValue(TimeDelta /* timeout */,
+                                      string* /* errmsg */) override {
+    return new UpdateRequestStatus(update_request_status_);
+  }
+
+  void Reset(bool forced_update_requested, bool is_interactive) {
+    UpdateRequestStatus new_value = UpdateRequestStatus::kNone;
+    if (forced_update_requested)
+      new_value = (is_interactive ? UpdateRequestStatus::kInteractive :
+                   UpdateRequestStatus::kPeriodic);
+    if (update_request_status_ != new_value) {
+      update_request_status_ = new_value;
+      NotifyValueChanged();
+    }
+  }
+
+  UpdateRequestStatus update_request_status_ = UpdateRequestStatus::kNone;
+
+  DISALLOW_COPY_AND_ASSIGN(ForcedUpdateRequestedVariable);
+};
+
+// RealUpdaterProvider methods.
+
+RealUpdaterProvider::RealUpdaterProvider(SystemState* system_state)
+  : system_state_(system_state),
+    var_updater_started_time_("updater_started_time",
+                              system_state->clock()->GetWallclockTime()),
+    var_last_checked_time_(
+        new LastCheckedTimeVariable("last_checked_time", system_state_)),
+    var_update_completed_time_(
+        new UpdateCompletedTimeVariable("update_completed_time",
+                                        system_state_)),
+    var_progress_(new ProgressVariable("progress", system_state_)),
+    var_stage_(new StageVariable("stage", system_state_)),
+    var_new_version_(new NewVersionVariable("new_version", system_state_)),
+    var_payload_size_(new PayloadSizeVariable("payload_size", system_state_)),
+    var_curr_channel_(new CurrChannelVariable("curr_channel", system_state_)),
+    var_new_channel_(new NewChannelVariable("new_channel", system_state_)),
+    var_p2p_enabled_(
+        new BooleanPrefVariable("p2p_enabled", system_state_,
+                                chromeos_update_engine::kPrefsP2PEnabled,
+                                false)),
+    var_cellular_enabled_(
+        new BooleanPrefVariable(
+            "cellular_enabled", system_state_,
+            chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+            false)),
+    var_consecutive_failed_update_checks_(
+        new ConsecutiveFailedUpdateChecksVariable(
+            "consecutive_failed_update_checks", system_state_)),
+    var_server_dictated_poll_interval_(
+        new ServerDictatedPollIntervalVariable(
+            "server_dictated_poll_interval", system_state_)),
+    var_forced_update_requested_(
+        new ForcedUpdateRequestedVariable(
+            "forced_update_requested", system_state_)) {}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.h b/update_manager/real_updater_provider.h
new file mode 100644
index 0000000..7317417
--- /dev/null
+++ b/update_manager/real_updater_provider.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// A concrete UpdaterProvider implementation using local (in-process) bindings.
+class RealUpdaterProvider : public UpdaterProvider {
+ public:
+  // We assume that any other object handle we get from the system state is
+  // "volatile", and so must be re-acquired whenever access is needed; this
+  // guarantees that parts of the system state can be mocked out at any time
+  // during testing. We further assume that, by the time Init() is called, the
+  // system state object is fully populated and usable.
+  explicit RealUpdaterProvider(
+      chromeos_update_engine::SystemState* system_state);
+
+  // Initializes the provider and returns whether it succeeded.
+  bool Init() { return true; }
+
+  Variable<base::Time>* var_updater_started_time() override {
+    return &var_updater_started_time_;
+  }
+
+  Variable<base::Time>* var_last_checked_time() override {
+    return var_last_checked_time_.get();
+  }
+
+  Variable<base::Time>* var_update_completed_time() override {
+    return var_update_completed_time_.get();
+  }
+
+  Variable<double>* var_progress() override {
+    return var_progress_.get();
+  }
+
+  Variable<Stage>* var_stage() override {
+    return var_stage_.get();
+  }
+
+  Variable<std::string>* var_new_version() override {
+    return var_new_version_.get();
+  }
+
+  Variable<int64_t>* var_payload_size() override {
+    return var_payload_size_.get();
+  }
+
+  Variable<std::string>* var_curr_channel() override {
+    return var_curr_channel_.get();
+  }
+
+  Variable<std::string>* var_new_channel() override {
+    return var_new_channel_.get();
+  }
+
+  Variable<bool>* var_p2p_enabled() override {
+    return var_p2p_enabled_.get();
+  }
+
+  Variable<bool>* var_cellular_enabled() override {
+    return var_cellular_enabled_.get();
+  }
+
+  Variable<unsigned int>* var_consecutive_failed_update_checks() override {
+    return var_consecutive_failed_update_checks_.get();
+  }
+
+  Variable<unsigned int>* var_server_dictated_poll_interval() override {
+    return var_server_dictated_poll_interval_.get();
+  }
+
+  Variable<UpdateRequestStatus>* var_forced_update_requested() override {
+    return var_forced_update_requested_.get();
+  }
+
+ private:
+  // A pointer to the update engine's system state aggregator.
+  chromeos_update_engine::SystemState* system_state_;
+
+  // Variable implementations.
+  ConstCopyVariable<base::Time> var_updater_started_time_;
+  std::unique_ptr<Variable<base::Time>> var_last_checked_time_;
+  std::unique_ptr<Variable<base::Time>> var_update_completed_time_;
+  std::unique_ptr<Variable<double>> var_progress_;
+  std::unique_ptr<Variable<Stage>> var_stage_;
+  std::unique_ptr<Variable<std::string>> var_new_version_;
+  std::unique_ptr<Variable<int64_t>> var_payload_size_;
+  std::unique_ptr<Variable<std::string>> var_curr_channel_;
+  std::unique_ptr<Variable<std::string>> var_new_channel_;
+  std::unique_ptr<Variable<bool>> var_p2p_enabled_;
+  std::unique_ptr<Variable<bool>> var_cellular_enabled_;
+  std::unique_ptr<Variable<unsigned int>> var_consecutive_failed_update_checks_;
+  std::unique_ptr<Variable<unsigned int>> var_server_dictated_poll_interval_;
+  std::unique_ptr<Variable<UpdateRequestStatus>> var_forced_update_requested_;
+
+  DISALLOW_COPY_AND_ASSIGN(RealUpdaterProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
diff --git a/update_manager/real_updater_provider_unittest.cc b/update_manager/real_updater_provider_unittest.cc
new file mode 100644
index 0000000..f1511dd
--- /dev/null
+++ b/update_manager/real_updater_provider_unittest.cc
@@ -0,0 +1,466 @@
+// Copyright (c) 2014 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/update_manager/real_updater_provider.h"
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#include <chromeos/dbus/service_constants.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/mock_prefs.h"
+#include "update_engine/mock_update_attempter.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::FakeClock;
+using chromeos_update_engine::FakeSystemState;
+using chromeos_update_engine::MockPrefs;
+using chromeos_update_engine::OmahaRequestParams;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::StrEq;
+using testing::_;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+  Time::Exploded now_exp;
+  now_exp.year = 2014;
+  now_exp.month = 3;
+  now_exp.day_of_week = 2;
+  now_exp.day_of_month = 18;
+  now_exp.hour = 8;
+  now_exp.minute = 5;
+  now_exp.second = 33;
+  now_exp.millisecond = 675;
+  return Time::FromLocalExploded(now_exp);
+}
+
+// Rounds down a timestamp to the nearest second. This is useful when faking
+// times that are converted to time_t (no sub-second resolution).
+Time RoundedToSecond(Time time) {
+  Time::Exploded exp;
+  time.LocalExplode(&exp);
+  exp.millisecond = 0;
+  return Time::FromLocalExploded(exp);
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealUpdaterProviderTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    fake_clock_ = fake_sys_state_.fake_clock();
+    provider_.reset(new RealUpdaterProvider(&fake_sys_state_));
+    ASSERT_NE(nullptr, provider_.get());
+    // Check that provider initializes correctly.
+    ASSERT_TRUE(provider_->Init());
+  }
+
+  // Sets up mock expectations for testing a variable that reads a Boolean pref
+  // |key|. |key_exists| determines whether the key is present. If it is, then
+  // |get_boolean_success| determines whether reading it is successful, and if
+  // so |output| is the value being read.
+  void SetupReadBooleanPref(const char* key, bool key_exists,
+                            bool get_boolean_success, bool output) {
+    MockPrefs* const mock_prefs = fake_sys_state_.mock_prefs();
+    EXPECT_CALL(*mock_prefs, Exists(StrEq(key))).WillOnce(Return(key_exists));
+    if (key_exists) {
+      auto& get_boolean = EXPECT_CALL(
+          *fake_sys_state_.mock_prefs(), GetBoolean(StrEq(key), _));
+      if (get_boolean_success)
+        get_boolean.WillOnce(DoAll(SetArgPointee<1>(output), Return(true)));
+      else
+        get_boolean.WillOnce(Return(false));
+    }
+  }
+
+  // Sets up mock expectations for testing the update completed time reporting.
+  // |valid| determines whether the returned time is valid. Returns the expected
+  // update completed time value.
+  Time SetupUpdateCompletedTime(bool valid) {
+    const TimeDelta kDurationSinceUpdate = TimeDelta::FromMinutes(7);
+    const Time kUpdateBootTime = Time() + kDurationSinceUpdate * 2;
+    const Time kCurrBootTime = (valid ?
+                                kUpdateBootTime + kDurationSinceUpdate :
+                                kUpdateBootTime - kDurationSinceUpdate);
+    const Time kCurrWallclockTime = FixedTime();
+    EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+                GetBootTimeAtUpdate(_))
+        .WillOnce(DoAll(SetArgPointee<0>(kUpdateBootTime), Return(true)));
+    fake_clock_->SetBootTime(kCurrBootTime);
+    fake_clock_->SetWallclockTime(kCurrWallclockTime);
+    return kCurrWallclockTime - kDurationSinceUpdate;
+  }
+
+  FakeSystemState fake_sys_state_;
+  FakeClock* fake_clock_;  // Short for fake_sys_state_.fake_clock()
+  unique_ptr<RealUpdaterProvider> provider_;
+};
+
+TEST_F(UmRealUpdaterProviderTest, UpdaterStartedTimeIsWallclockTime) {
+  fake_clock_->SetWallclockTime(Time::FromDoubleT(123.456));
+  fake_clock_->SetMonotonicTime(Time::FromDoubleT(456.123));
+  // Run SetUp again to re-setup the provider under test to use these values.
+  SetUp();
+  UmTestUtils::ExpectVariableHasValue(Time::FromDoubleT(123.456),
+                                      provider_->var_updater_started_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeOkay) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<0>(FixedTime().ToTimeT()), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(RoundedToSecond(FixedTime()),
+                                      provider_->var_last_checked_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_last_checked_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMin) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(0.0), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(0.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMid) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(0.3), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(0.3, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMax) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(1.0), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(1.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooSmall) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(-2.0), Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooBig) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(2.0), Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayIdle) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusIdle),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kIdle, provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayCheckingForUpdate) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusCheckingForUpdate),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kCheckingForUpdate,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdateAvailable) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusUpdateAvailable),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kUpdateAvailable,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayDownloading) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusDownloading),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kDownloading,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayVerifying) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusVerifying),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kVerifying,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayFinalizing) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusFinalizing),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kFinalizing,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdatedNeedReboot) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusUpdatedNeedReboot),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kUpdatedNeedReboot,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayReportingErrorEvent) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusReportingErrorEvent),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kReportingErrorEvent,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayAttemptingRollback) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(
+              SetArgPointee<2>(update_engine::kUpdateStatusAttemptingRollback),
+              Return(true)));
+  UmTestUtils::ExpectVariableHasValue(Stage::kAttemptingRollback,
+                                      provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailUnknown) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>("FooUpdateEngineState"),
+                      Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailEmpty) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<2>(""), Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionOkay) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<3>("1.2.0"), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(string("1.2.0"),
+                                      provider_->var_new_version());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_new_version());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayZero) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(0)), Return(true)));
+  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(0),
+                                      provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayArbitrary) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(567890)),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(567890),
+                                      provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayTwoGigabytes) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(1) << 31),
+                      Return(true)));
+  UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(1) << 31,
+                                      provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNegative) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              GetStatus(_, _, _, _, _))
+      .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(-1024)),
+                      Return(true)));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCurrChannelOkay) {
+  const string kChannelName("foo-channel");
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_current_channel(kChannelName);
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableHasValue(kChannelName,
+                                      provider_->var_curr_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCurrChannelFailEmpty) {
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_current_channel("");
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_curr_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewChannelOkay) {
+  const string kChannelName("foo-channel");
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_target_channel(kChannelName);
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableHasValue(kChannelName,
+                                      provider_->var_new_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewChannelFailEmpty) {
+  OmahaRequestParams request_params(&fake_sys_state_);
+  request_params.Init("", "", false);
+  request_params.set_target_channel("");
+  fake_sys_state_.set_request_params(&request_params);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_new_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefDoesntExist) {
+  SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+                       false, false, false);
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefReadsFalse) {
+  SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+                       true, true, false);
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefReadsTrue) {
+  SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+                       true, true, true);
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledFailCannotReadPref) {
+  SetupReadBooleanPref(chromeos_update_engine::kPrefsP2PEnabled,
+                       true, false, false);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefDoesntExist) {
+  SetupReadBooleanPref(
+      chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+      false, false, false);
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefReadsFalse) {
+  SetupReadBooleanPref(
+      chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+      true, true, false);
+  UmTestUtils::ExpectVariableHasValue(false, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefReadsTrue) {
+  SetupReadBooleanPref(
+      chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+      true, true, true);
+  UmTestUtils::ExpectVariableHasValue(true, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledFailCannotReadPref) {
+  SetupReadBooleanPref(
+      chromeos_update_engine::kPrefsUpdateOverCellularPermission,
+      true, false, false);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeOkay) {
+  Time expected = SetupUpdateCompletedTime(true);
+  UmTestUtils::ExpectVariableHasValue(expected,
+                                      provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailNoValue) {
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetBootTimeAtUpdate(_))
+      .WillOnce(Return(false));
+  UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailInvalidValue) {
+  SetupUpdateCompletedTime(false);
+  UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetConsecutiveFailedUpdateChecks) {
+  const unsigned int kNumFailedChecks = 3;
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              consecutive_failed_update_checks())
+      .WillRepeatedly(Return(kNumFailedChecks));
+  UmTestUtils::ExpectVariableHasValue(
+      kNumFailedChecks, provider_->var_consecutive_failed_update_checks());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetServerDictatedPollInterval) {
+  const unsigned int kPollInterval = 2 * 60 * 60;  // Two hours.
+  EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+              server_dictated_poll_interval())
+      .WillRepeatedly(Return(kPollInterval));
+  UmTestUtils::ExpectVariableHasValue(
+      kPollInterval, provider_->var_server_dictated_poll_interval());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/shill_provider.h b/update_manager/shill_provider.h
new file mode 100644
index 0000000..89d3b97
--- /dev/null
+++ b/update_manager/shill_provider.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+enum class ConnectionType {
+  kEthernet,
+  kWifi,
+  kWimax,
+  kBluetooth,
+  kCellular,
+  kUnknown
+};
+
+enum class ConnectionTethering {
+  kNotDetected,
+  kSuspected,
+  kConfirmed,
+  kUnknown,
+};
+
+// Provider for networking related information.
+class ShillProvider : public Provider {
+ public:
+  ~ShillProvider() override {}
+
+  // A variable returning whether we currently have network connectivity.
+  virtual Variable<bool>* var_is_connected() = 0;
+
+  // A variable returning the current network connection type. Unknown if not
+  // connected.
+  virtual Variable<ConnectionType>* var_conn_type() = 0;
+
+  // A variable returning the tethering mode of a network connection. Unknown if
+  // not connected.
+  virtual Variable<ConnectionTethering>* var_conn_tethering() = 0;
+
+  // A variable returning the time when network connection last changed.
+  // Initialized to current time.
+  virtual Variable<base::Time>* var_conn_last_changed() = 0;
+
+ protected:
+  ShillProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ShillProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
diff --git a/update_manager/state.h b/update_manager/state.h
new file mode 100644
index 0000000..fdbb38b
--- /dev/null
+++ b/update_manager/state.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/random_provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/system_provider.h"
+#include "update_engine/update_manager/time_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// The State class is an interface to the ensemble of providers. This class
+// gives visibility of the state providers to policy implementations.
+class State {
+ public:
+  virtual ~State() {}
+
+  // These methods return the given provider.
+  virtual ConfigProvider* config_provider() = 0;
+  virtual DevicePolicyProvider* device_policy_provider() = 0;
+  virtual RandomProvider* random_provider() = 0;
+  virtual ShillProvider* shill_provider() = 0;
+  virtual SystemProvider* system_provider() = 0;
+  virtual TimeProvider* time_provider() = 0;
+  virtual UpdaterProvider* updater_provider() = 0;
+
+ protected:
+  State() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(State);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
diff --git a/update_manager/state_factory.cc b/update_manager/state_factory.cc
new file mode 100644
index 0000000..54c0205
--- /dev/null
+++ b/update_manager/state_factory.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2014 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/update_manager/state_factory.h"
+
+#include <memory>
+
+#include <base/logging.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/real_config_provider.h"
+#include "update_engine/update_manager/real_device_policy_provider.h"
+#include "update_engine/update_manager/real_random_provider.h"
+#include "update_engine/update_manager/real_shill_provider.h"
+#include "update_engine/update_manager/real_state.h"
+#include "update_engine/update_manager/real_system_provider.h"
+#include "update_engine/update_manager/real_time_provider.h"
+#include "update_engine/update_manager/real_updater_provider.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+State* DefaultStateFactory(policy::PolicyProvider* policy_provider,
+                           chromeos_update_engine::DBusWrapperInterface* dbus,
+                           chromeos_update_engine::SystemState* system_state) {
+  chromeos_update_engine::ClockInterface* const clock = system_state->clock();
+  unique_ptr<RealConfigProvider> config_provider(
+      new RealConfigProvider(system_state->hardware()));
+  unique_ptr<RealDevicePolicyProvider> device_policy_provider(
+      new RealDevicePolicyProvider(dbus, policy_provider));
+  unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider());
+  unique_ptr<RealShillProvider> shill_provider(
+      new RealShillProvider(dbus, clock));
+  unique_ptr<RealSystemProvider> system_provider(
+      new RealSystemProvider(system_state->hardware()));
+  unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock));
+  unique_ptr<RealUpdaterProvider> updater_provider(
+      new RealUpdaterProvider(system_state));
+
+  if (!(config_provider->Init() &&
+        device_policy_provider->Init() &&
+        random_provider->Init() &&
+        shill_provider->Init() &&
+        system_provider->Init() &&
+        time_provider->Init() &&
+        updater_provider->Init())) {
+    LOG(ERROR) << "Error initializing providers";
+    return nullptr;
+  }
+
+  return new RealState(config_provider.release(),
+                       device_policy_provider.release(),
+                       random_provider.release(),
+                       shill_provider.release(),
+                       system_provider.release(),
+                       time_provider.release(),
+                       updater_provider.release());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/state_factory.h b/update_manager/state_factory.h
new file mode 100644
index 0000000..fa1164c
--- /dev/null
+++ b/update_manager/state_factory.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_STATE_FACTORY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
+
+#include "update_engine/dbus_wrapper_interface.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// Creates and initializes a new UpdateManager State instance containing real
+// providers instantiated using the passed interfaces. The State doesn't take
+// ownership of the passed interfaces, which need to remain available during the
+// life of this instance.  Returns null if one of the underlying providers fails
+// to initialize.
+State* DefaultStateFactory(
+    policy::PolicyProvider* policy_provider,
+    chromeos_update_engine::DBusWrapperInterface* dbus,
+    chromeos_update_engine::SystemState* system_state);
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
diff --git a/update_manager/system_provider.h b/update_manager/system_provider.h
new file mode 100644
index 0000000..ea5f79f
--- /dev/null
+++ b/update_manager/system_provider.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for system information, mostly constant, such as the information
+// reported by crossystem, the kernel boot command line and the partition table.
+class SystemProvider : public Provider {
+ public:
+  ~SystemProvider() override {}
+
+  // Returns true if the boot mode is normal or if it's unable to
+  // determine the boot mode. Returns false if the boot mode is
+  // developer.
+  virtual Variable<bool>* var_is_normal_boot_mode() = 0;
+
+  // Returns whether this is an official Chrome OS build.
+  virtual Variable<bool>* var_is_official_build() = 0;
+
+  // Returns a variable that tells whether OOBE was completed.
+  virtual Variable<bool>* var_is_oobe_complete() = 0;
+
+  // Returns a variable that tells the boot device is removable (USB stick etc).
+  virtual Variable<bool>* var_is_boot_device_removable() = 0;
+
+ protected:
+  SystemProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SystemProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
diff --git a/update_manager/time_provider.h b/update_manager/time_provider.h
new file mode 100644
index 0000000..a30fdb3
--- /dev/null
+++ b/update_manager/time_provider.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for time related information.
+class TimeProvider : public Provider {
+ public:
+  ~TimeProvider() override {}
+
+  // Returns the current date. The time of day component will be zero.
+  virtual Variable<base::Time>* var_curr_date() = 0;
+
+  // Returns the current hour (0 to 23) in local time. The type is int to keep
+  // consistent with base::Time.
+  virtual Variable<int>* var_curr_hour() = 0;
+
+ protected:
+  TimeProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TimeProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
diff --git a/update_manager/umtest_utils.cc b/update_manager/umtest_utils.cc
new file mode 100644
index 0000000..be6a86f
--- /dev/null
+++ b/update_manager/umtest_utils.cc
@@ -0,0 +1,17 @@
+// Copyright (c) 2014 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/update_manager/umtest_utils.h"
+
+#include <base/time/time.h>
+
+namespace chromeos_update_manager {
+
+const unsigned UmTestUtils::kDefaultTimeoutInSeconds = 1;
+
+void PrintTo(const EvalStatus& status, ::std::ostream* os) {
+  *os << ToString(status);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/umtest_utils.h b/update_manager/umtest_utils.h
new file mode 100644
index 0000000..c50a5a2
--- /dev/null
+++ b/update_manager/umtest_utils.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_UMTEST_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
+
+#include <iostream>  // NOLINT(readability/streams)
+#include <memory>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A help class with common functionality for use in Update Manager testing.
+class UmTestUtils {
+ public:
+  // A default timeout to use when making various queries.
+  static const base::TimeDelta DefaultTimeout() {
+    return base::TimeDelta::FromSeconds(kDefaultTimeoutInSeconds);
+  }
+
+  // Calls GetValue on |variable| and expects its result to be |expected|.
+  template<typename T>
+  static void ExpectVariableHasValue(const T& expected, Variable<T>* variable) {
+    ASSERT_NE(nullptr, variable);
+    std::unique_ptr<const T> value(
+        variable->GetValue(DefaultTimeout(), nullptr));
+    ASSERT_NE(nullptr, value.get()) << "Variable: " << variable->GetName();
+    EXPECT_EQ(expected, *value) << "Variable: " << variable->GetName();
+  }
+
+  // Calls GetValue on |variable| and expects its result to be null.
+  template<typename T>
+  static void ExpectVariableNotSet(Variable<T>* variable) {
+    ASSERT_NE(nullptr, variable);
+    std::unique_ptr<const T> value(
+        variable->GetValue(DefaultTimeout(), nullptr));
+    EXPECT_EQ(nullptr, value.get()) << "Variable: " << variable->GetName();
+  }
+
+ private:
+  static const unsigned kDefaultTimeoutInSeconds;
+};
+
+// PrintTo() functions are used by gtest to print these values. They need to be
+// defined on the same namespace where the type was defined.
+void PrintTo(const EvalStatus& status, ::std::ostream* os);
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
diff --git a/update_manager/update_manager-inl.h b/update_manager/update_manager-inl.h
new file mode 100644
index 0000000..b8fef28
--- /dev/null
+++ b/update_manager/update_manager-inl.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/update_manager/evaluation_context.h"
+
+namespace chromeos_update_manager {
+
+template<typename R, typename... Args>
+EvalStatus UpdateManager::EvaluatePolicy(
+    EvaluationContext* ec,
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        Args...) const,
+    R* result, Args... args) {
+  // If expiration timeout fired, dump the context and reset expiration.
+  // IMPORTANT: We must still proceed with evaluation of the policy in this
+  // case, so that the evaluation time (and corresponding reevaluation timeouts)
+  // are readjusted.
+  if (ec->is_expired()) {
+    LOG(WARNING) << "Request timed out, evaluation context: "
+                 << ec->DumpContext();
+    ec->ResetExpiration();
+  }
+
+  // Reset the evaluation context.
+  ec->ResetEvaluation();
+
+  const std::string policy_name = policy_->PolicyRequestName(policy_method);
+  LOG(INFO) << policy_name << ": START";
+
+  // First try calling the actual policy.
+  std::string error;
+  EvalStatus status = (policy_.get()->*policy_method)(ec, state_.get(), &error,
+                                                      result, args...);
+  // If evaluating the main policy failed, defer to the default policy.
+  if (status == EvalStatus::kFailed) {
+    LOG(WARNING) << "Evaluating policy failed: " << error
+                 << "\nEvaluation context: " << ec->DumpContext();
+    error.clear();
+    status = (default_policy_.*policy_method)(ec, state_.get(), &error, result,
+                                              args...);
+    if (status == EvalStatus::kFailed) {
+      LOG(WARNING) << "Evaluating default policy failed: " << error;
+    } else if (status == EvalStatus::kAskMeAgainLater) {
+      LOG(ERROR)
+          << "Default policy would block; this is a bug, forcing failure.";
+      status = EvalStatus::kFailed;
+    }
+  }
+
+  LOG(INFO) << policy_name << ": END";
+
+  return status;
+}
+
+template<typename R, typename... Args>
+void UpdateManager::OnPolicyReadyToEvaluate(
+    scoped_refptr<EvaluationContext> ec,
+    base::Callback<void(EvalStatus status, const R& result)> callback,
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        Args...) const,
+    Args... args) {
+  // Evaluate the policy.
+  R result;
+  EvalStatus status = EvaluatePolicy(ec.get(), policy_method, &result, args...);
+
+  if (status != EvalStatus::kAskMeAgainLater) {
+    // AsyncPolicyRequest finished.
+    callback.Run(status, result);
+    return;
+  }
+
+  // Re-schedule the policy request based on used variables.
+  base::Closure reeval_callback = base::Bind(
+      &UpdateManager::OnPolicyReadyToEvaluate<R, Args...>,
+      base::Unretained(this), ec, callback,
+      policy_method, args...);
+  if (ec->RunOnValueChangeOrTimeout(reeval_callback))
+    return;  // Reevaluation scheduled successfully.
+
+  // Scheduling a reevaluation can fail because policy method didn't use any
+  // non-const variable nor there's any time-based event that will change the
+  // status of evaluation.  Alternatively, this may indicate an error in the use
+  // of the scheduling interface.
+  LOG(ERROR) << "Failed to schedule a reevaluation of policy "
+             << policy_->PolicyRequestName(policy_method) << "; this is a bug.";
+  callback.Run(status, result);
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+EvalStatus UpdateManager::PolicyRequest(
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        ExpectedArgs...) const,
+    R* result, ActualArgs... args) {
+  scoped_refptr<EvaluationContext> ec(
+      new EvaluationContext(clock_, evaluation_timeout_));
+  // A PolicyRequest always consists on a single evaluation on a new
+  // EvaluationContext.
+  // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
+  // explicitly instantiate EvaluatePolicy with the latter in lieu of the
+  // former.
+  EvalStatus ret = EvaluatePolicy<R, ExpectedArgs...>(ec.get(), policy_method,
+                                                      result, args...);
+  // Sync policy requests must not block, if they do then this is an error.
+  DCHECK(EvalStatus::kAskMeAgainLater != ret);
+  LOG_IF(WARNING, EvalStatus::kAskMeAgainLater == ret)
+      << "Sync request used with an async policy; this is a bug";
+  return ret;
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+void UpdateManager::AsyncPolicyRequest(
+    base::Callback<void(EvalStatus, const R& result)> callback,
+    EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                        std::string*, R*,
+                                        ExpectedArgs...) const,
+    ActualArgs... args) {
+  scoped_refptr<EvaluationContext> ec =
+      new EvaluationContext(
+          clock_, evaluation_timeout_, expiration_timeout_,
+          std::unique_ptr<base::Callback<void(EvaluationContext*)>>(
+              new base::Callback<void(EvaluationContext*)>(
+                  base::Bind(&UpdateManager::UnregisterEvalContext,
+                             weak_ptr_factory_.GetWeakPtr()))));
+  if (!ec_repo_.insert(ec.get()).second) {
+    LOG(ERROR) << "Failed to register evaluation context; this is a bug.";
+  }
+
+  // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
+  // explicitly instantiate UpdateManager::OnPolicyReadyToEvaluate with the
+  // latter in lieu of the former.
+  base::Closure eval_callback = base::Bind(
+      &UpdateManager::OnPolicyReadyToEvaluate<R, ExpectedArgs...>,
+      base::Unretained(this), ec, callback, policy_method, args...);
+  chromeos::MessageLoop::current()->PostTask(FROM_HERE, eval_callback);
+}
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
diff --git a/update_manager/update_manager.cc b/update_manager/update_manager.cc
new file mode 100644
index 0000000..13c208e
--- /dev/null
+++ b/update_manager/update_manager.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2014 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/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/chromeos_policy.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+UpdateManager::UpdateManager(chromeos_update_engine::ClockInterface* clock,
+                             base::TimeDelta evaluation_timeout,
+                             base::TimeDelta expiration_timeout, State* state)
+      : default_policy_(clock), state_(state), clock_(clock),
+        evaluation_timeout_(evaluation_timeout),
+        expiration_timeout_(expiration_timeout),
+        weak_ptr_factory_(this) {
+  // TODO(deymo): Make it possible to replace this policy with a different
+  // implementation with a build-time flag.
+  policy_.reset(new ChromeOSPolicy());
+}
+
+UpdateManager::~UpdateManager() {
+  // Remove pending main loop events associated with any of the outstanding
+  // evaluation contexts. This will prevent dangling pending events, causing
+  // these contexts to be destructed once the repo itself is destructed.
+  for (auto& ec : ec_repo_)
+    ec->RemoveObserversAndTimeout();
+}
+
+void UpdateManager::UnregisterEvalContext(EvaluationContext* ec) {
+  if (!ec_repo_.erase(ec)) {
+    LOG(ERROR) << "Unregistering an unknown evaluation context, this is a bug.";
+  }
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/update_manager.conf.example b/update_manager/update_manager.conf.example
new file mode 100644
index 0000000..2d77974
--- /dev/null
+++ b/update_manager/update_manager.conf.example
@@ -0,0 +1,18 @@
+# Configuration file for the update-manager component of update_engine.
+#
+# Normally this file is loaded from /etc/update_manager.conf. If
+# running update_engine in developer mode (and only if running in
+# developer mode), we attempt to load
+#
+#  /mnt/stateful_partition/etc/update_manager.conf
+#
+# and use it if it exists. If it doesn't exist, we fall back to
+# /etc/update_manager.conf.
+#
+# Note: changes to this file are not automatically applied. Use the
+# command "restart update-engine" from a root shell to make your
+# changes take effect.
+
+# Set to true if the device supports the concept of OOBE
+# (Out-Of-the-Box-Experience), false if it doesn't.
+is_oobe_enabled=true
diff --git a/update_manager/update_manager.h b/update_manager/update_manager.h
new file mode 100644
index 0000000..cf7c6d5
--- /dev/null
+++ b/update_manager/update_manager.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// Comparator for scoped_refptr objects.
+template<typename T>
+struct ScopedRefPtrLess {
+  bool operator()(const scoped_refptr<T>& first,
+                  const scoped_refptr<T>& second) const {
+    return first.get() < second.get();
+  }
+};
+
+// The main Update Manager singleton class.
+class UpdateManager {
+ public:
+  // Creates the UpdateManager instance, assuming ownership on the provided
+  // |state|.
+  UpdateManager(chromeos_update_engine::ClockInterface* clock,
+                base::TimeDelta evaluation_timeout,
+                base::TimeDelta expiration_timeout, State* state);
+
+  virtual ~UpdateManager();
+
+  // PolicyRequest() evaluates the given policy with the provided arguments and
+  // returns the result. The |policy_method| is the pointer-to-method of the
+  // Policy class for the policy request to call. The UpdateManager will call
+  // this method on the right policy. The pointer |result| must not be null
+  // and the remaining |args| depend on the arguments required by the passed
+  // |policy_method|.
+  //
+  // When the policy request succeeds, the |result| is set and the method
+  // returns EvalStatus::kSucceeded, otherwise, the |result| may not be set.  A
+  // policy called with this method should not block (i.e. return
+  // EvalStatus::kAskMeAgainLater), which is considered a programming error. On
+  // failure, EvalStatus::kFailed is returned.
+  //
+  // An example call to this method is:
+  //   um.PolicyRequest(&Policy::SomePolicyMethod, &bool_result, arg1, arg2);
+  template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+  EvalStatus PolicyRequest(
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          ExpectedArgs...) const,
+      R* result, ActualArgs...);
+
+  // Evaluates the given |policy_method| policy with the provided |args|
+  // arguments and calls the |callback| callback with the result when done.
+  //
+  // If the policy implementation should block, returning a
+  // EvalStatus::kAskMeAgainLater status the Update Manager will re-evaluate the
+  // policy until another status is returned. If the policy implementation based
+  // its return value solely on const variables, the callback will be called
+  // with the EvalStatus::kAskMeAgainLater status (which indicates an error).
+  template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+  void AsyncPolicyRequest(
+      base::Callback<void(EvalStatus, const R& result)> callback,
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          ExpectedArgs...) const,
+      ActualArgs... args);
+
+ protected:
+  // The UpdateManager receives ownership of the passed Policy instance.
+  void set_policy(const Policy* policy) {
+    policy_.reset(policy);
+  }
+
+  // State getter used for testing.
+  State* state() { return state_.get(); }
+
+ private:
+  FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsPolicy);
+  FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError);
+  FRIEND_TEST(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest);
+  FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation);
+  FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire);
+  FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimesOut);
+
+  // EvaluatePolicy() evaluates the passed |policy_method| method on the current
+  // policy with the given |args| arguments. If the method fails, the default
+  // policy is used instead.
+  template<typename R, typename... Args>
+  EvalStatus EvaluatePolicy(
+      EvaluationContext* ec,
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          Args...) const,
+      R* result, Args... args);
+
+  // OnPolicyReadyToEvaluate() is called by the main loop when the evaluation
+  // of the given |policy_method| should be executed. If the evaluation finishes
+  // the |callback| callback is called passing the |result| and the |status|
+  // returned by the policy. If the evaluation returns an
+  // EvalStatus::kAskMeAgainLater state, the |callback| will NOT be called and
+  // the evaluation will be re-scheduled to be called later.
+  template<typename R, typename... Args>
+  void OnPolicyReadyToEvaluate(
+      scoped_refptr<EvaluationContext> ec,
+      base::Callback<void(EvalStatus status, const R& result)> callback,
+      EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+                                          std::string*, R*,
+                                          Args...) const,
+      Args... args);
+
+  // Unregisters (removes from repo) a previously created EvaluationContext.
+  void UnregisterEvalContext(EvaluationContext* ec);
+
+  // The policy used by the UpdateManager. Note that since it is a const Policy,
+  // policy implementations are not allowed to persist state on this class.
+  std::unique_ptr<const Policy> policy_;
+
+  // A safe default value to the current policy. This policy is used whenever
+  // a policy implementation fails with EvalStatus::kFailed.
+  const DefaultPolicy default_policy_;
+
+  // State Providers.
+  std::unique_ptr<State> state_;
+
+  // Pointer to the mockable clock interface;
+  chromeos_update_engine::ClockInterface* clock_;
+
+  // Timeout for a policy evaluation.
+  const base::TimeDelta evaluation_timeout_;
+
+  // Timeout for expiration of the evaluation context, used for async requests.
+  const base::TimeDelta expiration_timeout_;
+
+  // Repository of previously created EvaluationContext objects. These are being
+  // unregistered (and the reference released) when the context is being
+  // destructed; alternatively, when the UpdateManager instance is destroyed, it
+  // will remove all pending events associated with all outstanding contexts
+  // (which should, in turn, trigger their destruction).
+  std::set<scoped_refptr<EvaluationContext>,
+           ScopedRefPtrLess<EvaluationContext>> ec_repo_;
+
+  base::WeakPtrFactory<UpdateManager> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateManager);
+};
+
+}  // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/update_manager-inl.h"
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
diff --git a/update_manager/update_manager_unittest.cc b/update_manager/update_manager_unittest.cc
new file mode 100644
index 0000000..cf097fa
--- /dev/null
+++ b/update_manager/update_manager_unittest.cc
@@ -0,0 +1,313 @@
+// Copyright (c) 2014 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/update_manager/update_manager.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/test/simple_test_clock.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/mock_policy.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::Time;
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::pair;
+using std::string;
+using std::tuple;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+  Time::Exploded now_exp;
+  now_exp.year = 2014;
+  now_exp.month = 3;
+  now_exp.day_of_week = 2;
+  now_exp.day_of_month = 18;
+  now_exp.hour = 8;
+  now_exp.minute = 5;
+  now_exp.second = 33;
+  now_exp.millisecond = 675;
+  return Time::FromLocalExploded(now_exp);
+}
+
+}  // namespace
+
+namespace chromeos_update_manager {
+
+class UmUpdateManagerTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+    fake_state_ = new FakeState();
+    umut_.reset(new UpdateManager(&fake_clock_, TimeDelta::FromSeconds(5),
+                                  TimeDelta::FromSeconds(1), fake_state_));
+  }
+
+  void TearDown() override {
+    EXPECT_FALSE(loop_.PendingTasks());
+  }
+
+  base::SimpleTestClock test_clock_;
+  chromeos::FakeMessageLoop loop_{&test_clock_};
+  FakeState* fake_state_;  // Owned by the umut_.
+  FakeClock fake_clock_;
+  unique_ptr<UpdateManager> umut_;
+};
+
+// The FailingPolicy implements a single method and make it always fail. This
+// class extends the DefaultPolicy class to allow extensions of the Policy
+// class without extending nor changing this test.
+class FailingPolicy : public DefaultPolicy {
+ public:
+  explicit FailingPolicy(int* num_called_p) : num_called_p_(num_called_p) {}
+  FailingPolicy() : FailingPolicy(nullptr) {}
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+                                string* error,
+                                UpdateCheckParams* result) const override {
+    if (num_called_p_)
+      (*num_called_p_)++;
+    *error = "FailingPolicy failed.";
+    return EvalStatus::kFailed;
+  }
+
+ protected:
+  string PolicyName() const override { return "FailingPolicy"; }
+
+ private:
+  int* num_called_p_;
+};
+
+// The LazyPolicy always returns EvalStatus::kAskMeAgainLater.
+class LazyPolicy : public DefaultPolicy {
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+                                string* error,
+                                UpdateCheckParams* result) const override {
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+ protected:
+  string PolicyName() const override { return "LazyPolicy"; }
+};
+
+// A policy that sleeps for a predetermined amount of time, then checks for a
+// wallclock-based time threshold (if given) and returns
+// EvalStatus::kAskMeAgainLater if not passed; otherwise, returns
+// EvalStatus::kSucceeded. Increments a counter every time it is being queried,
+// if a pointer to it is provided.
+class DelayPolicy : public DefaultPolicy {
+ public:
+  DelayPolicy(int sleep_secs, Time time_threshold, int* num_called_p)
+      : sleep_secs_(sleep_secs), time_threshold_(time_threshold),
+        num_called_p_(num_called_p) {}
+  EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+                                string* error,
+                                UpdateCheckParams* result) const override {
+    if (num_called_p_)
+      (*num_called_p_)++;
+
+    // Sleep for a predetermined amount of time.
+    if (sleep_secs_ > 0)
+      sleep(sleep_secs_);
+
+    // Check for a time threshold. This can be used to ensure that the policy
+    // has some non-constant dependency.
+    if (time_threshold_ < Time::Max() &&
+        ec->IsWallclockTimeGreaterThan(time_threshold_))
+      return EvalStatus::kSucceeded;
+
+    return EvalStatus::kAskMeAgainLater;
+  }
+
+ protected:
+  string PolicyName() const override { return "DelayPolicy"; }
+
+ private:
+  int sleep_secs_;
+  Time time_threshold_;
+  int* num_called_p_;
+};
+
+// AccumulateCallsCallback() adds to the passed |acc| accumulator vector pairs
+// of EvalStatus and T instances. This allows to create a callback that keeps
+// track of when it is called and the arguments passed to it, to be used with
+// the UpdateManager::AsyncPolicyRequest().
+template<typename T>
+static void AccumulateCallsCallback(vector<pair<EvalStatus, T>>* acc,
+                                    EvalStatus status, const T& result) {
+  acc->push_back(std::make_pair(status, result));
+}
+
+// Tests that policy requests are completed successfully. It is important that
+// this tests cover all policy requests as defined in Policy.
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCheckAllowed) {
+  UpdateCheckParams result;
+  EXPECT_EQ(EvalStatus::kSucceeded, umut_->PolicyRequest(
+      &Policy::UpdateCheckAllowed, &result));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCanStart) {
+  UpdateState update_state = UpdateState();
+  update_state.is_interactive = true;
+  update_state.is_delta_payload = false;
+  update_state.first_seen = FixedTime();
+  update_state.num_checks = 1;
+  update_state.num_failures = 0;
+  update_state.failures_last_updated = Time();
+  update_state.download_urls = vector<string>{"http://fake/url/"};
+  update_state.download_errors_max = 10;
+  update_state.p2p_downloading_disabled = false;
+  update_state.p2p_sharing_disabled = false;
+  update_state.p2p_num_attempts = 0;
+  update_state.p2p_first_attempted = Time();
+  update_state.last_download_url_idx = -1;
+  update_state.last_download_url_num_errors = 0;
+  update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+  update_state.backoff_expiry = Time();
+  update_state.is_backoff_disabled = false;
+  update_state.scatter_wait_period = TimeDelta::FromSeconds(15);
+  update_state.scatter_check_threshold = 4;
+  update_state.scatter_wait_period_max = TimeDelta::FromSeconds(60);
+  update_state.scatter_check_threshold_min = 2;
+  update_state.scatter_check_threshold_max = 8;
+
+  UpdateDownloadParams result;
+  EXPECT_EQ(EvalStatus::kSucceeded,
+            umut_->PolicyRequest(&Policy::UpdateCanStart, &result,
+                                 update_state));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError) {
+  umut_->set_policy(new FailingPolicy());
+
+  // Tests that the DefaultPolicy instance is called when the method fails,
+  // which will set this as true.
+  UpdateCheckParams result;
+  result.updates_enabled = false;
+  EvalStatus status = umut_->PolicyRequest(
+      &Policy::UpdateCheckAllowed, &result);
+  EXPECT_EQ(EvalStatus::kSucceeded, status);
+  EXPECT_TRUE(result.updates_enabled);
+}
+
+// This test only applies to debug builds where DCHECK is enabled.
+#if DCHECK_IS_ON
+TEST_F(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest) {
+  // The update manager should die (DCHECK) if a policy called synchronously
+  // returns a kAskMeAgainLater value.
+  UpdateCheckParams result;
+  umut_->set_policy(new LazyPolicy());
+  EXPECT_DEATH(umut_->PolicyRequest(&Policy::UpdateCheckAllowed, &result), "");
+}
+#endif  // DCHECK_IS_ON
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation) {
+  // To avoid differences in code execution order between an AsyncPolicyRequest
+  // call on a policy that returns AskMeAgainLater the first time and one that
+  // succeeds the first time, we ensure that the passed callback is called from
+  // the main loop in both cases even when we could evaluate it right now.
+  umut_->set_policy(new FailingPolicy());
+
+  vector<pair<EvalStatus, UpdateCheckParams>> calls;
+  Callback<void(EvalStatus, const UpdateCheckParams&)> callback = Bind(
+      AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+  umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  // The callback should wait until we run the main loop for it to be executed.
+  EXPECT_EQ(0, calls.size());
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire) {
+  // Set up an async policy call to return immediately, then wait a little and
+  // ensure that the timeout event does not fire.
+  int num_called = 0;
+  umut_->set_policy(new FailingPolicy(&num_called));
+
+  vector<pair<EvalStatus, UpdateCheckParams>> calls;
+  Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+      Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+  umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  // Run the main loop, ensure that policy was attempted once before deferring
+  // to the default.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, num_called);
+  ASSERT_EQ(1, calls.size());
+  EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+  // Wait for the timeout to expire, run the main loop again, ensure that
+  // nothing happened.
+  test_clock_.Advance(TimeDelta::FromSeconds(2));
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_EQ(1, num_called);
+  EXPECT_EQ(1, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimesOut) {
+  // Set up an async policy call to exceed its expiration timeout, make sure
+  // that the default policy was not used (no callback) and that evaluation is
+  // reattempted.
+  int num_called = 0;
+  umut_->set_policy(new DelayPolicy(
+          0, fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(3),
+          &num_called));
+
+  vector<pair<EvalStatus, UpdateCheckParams>> calls;
+  Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+      Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+  umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+  // Run the main loop, ensure that policy was attempted once but the callback
+  // was not invoked.
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+  EXPECT_EQ(1, num_called);
+  EXPECT_EQ(0, calls.size());
+  // Wait for the expiration timeout to expire, run the main loop again,
+  // ensure that reevaluation occurred but callback was not invoked (i.e.
+  // default policy was not consulted).
+  test_clock_.Advance(TimeDelta::FromSeconds(2));
+  fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() +
+                               TimeDelta::FromSeconds(2));
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_EQ(2, num_called);
+  EXPECT_EQ(0, calls.size());
+  // Wait for reevaluation due to delay to happen, ensure that it occurs and
+  // that the callback is invoked.
+  test_clock_.Advance(TimeDelta::FromSeconds(2));
+  fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() +
+                               TimeDelta::FromSeconds(2));
+  MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+  EXPECT_EQ(3, num_called);
+  ASSERT_EQ(1, calls.size());
+  EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/updater_provider.h b/update_manager/updater_provider.h
new file mode 100644
index 0000000..1ff1085
--- /dev/null
+++ b/update_manager/updater_provider.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+enum class Stage {
+  kIdle,
+  kCheckingForUpdate,
+  kUpdateAvailable,
+  kDownloading,
+  kVerifying,
+  kFinalizing,
+  kUpdatedNeedReboot,
+  kReportingErrorEvent,
+  kAttemptingRollback,
+};
+
+enum class UpdateRequestStatus {
+  kNone,
+  kInteractive,
+  kPeriodic,
+};
+
+// Provider for Chrome OS update related information.
+class UpdaterProvider : public Provider {
+ public:
+  ~UpdaterProvider() override {}
+
+  // A variable returning the timestamp when the update engine was started in
+  // wallclock time.
+  virtual Variable<base::Time>* var_updater_started_time() = 0;
+
+  // A variable returning the last update check time.
+  virtual Variable<base::Time>* var_last_checked_time() = 0;
+
+  // A variable reporting the time when an update was last completed in the
+  // current boot cycle. Returns an error if an update completed time could not
+  // be read (e.g. no update was completed in the current boot cycle) or is
+  // invalid.
+  //
+  // IMPORTANT: The time reported is not the wallclock time reading at the time
+  // of the update, rather it is the point in time when the update completed
+  // relative to the current wallclock time reading. Therefore, the gap between
+  // the reported value and the current wallclock time is guaranteed to be
+  // monotonically increasing.
+  virtual Variable<base::Time>* var_update_completed_time() = 0;
+
+  // A variable returning the update progress (0.0 to 1.0).
+  virtual Variable<double>* var_progress() = 0;
+
+  // A variable returning the current update status.
+  virtual Variable<Stage>* var_stage() = 0;
+
+  // A variable returning the update target version.
+  virtual Variable<std::string>* var_new_version() = 0;
+
+  // A variable returning the update payload size. The payload size is
+  // guaranteed to be non-negative.
+  virtual Variable<int64_t>* var_payload_size() = 0;
+
+  // A variable returning the current channel.
+  virtual Variable<std::string>* var_curr_channel() = 0;
+
+  // A variable returning the update target channel.
+  virtual Variable<std::string>* var_new_channel() = 0;
+
+  // A variable indicating whether user settings allow P2P updates.
+  virtual Variable<bool>* var_p2p_enabled() = 0;
+
+  // A variable indicating whether user settings allow updates over a cellular
+  // network.
+  virtual Variable<bool>* var_cellular_enabled() = 0;
+
+  // A variable returning the number of consecutive failed update checks.
+  virtual Variable<unsigned int>* var_consecutive_failed_update_checks() = 0;
+
+  // A server-dictated update check interval in seconds, if one was given.
+  virtual Variable<unsigned int>* var_server_dictated_poll_interval() = 0;
+
+  // A variable denoting whether a forced update was request but no update check
+  // performed yet; also tells whether this request is for an interactive or
+  // scheduled update.
+  virtual Variable<UpdateRequestStatus>* var_forced_update_requested() = 0;
+
+ protected:
+  UpdaterProvider() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdaterProvider);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
diff --git a/update_manager/variable.h b/update_manager/variable.h
new file mode 100644
index 0000000..7ce2af8
--- /dev/null
+++ b/update_manager/variable.h
@@ -0,0 +1,205 @@
+// Copyright (c) 2014 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_UPDATE_MANAGER_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
+
+#include <algorithm>
+#include <list>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace chromeos_update_manager {
+
+// The VariableMode specifies important behavior of the variable in terms of
+// whether, how and when the value of the variable changes.
+enum VariableMode {
+  // Const variables never changes during the life of a policy request, so the
+  // EvaluationContext caches the value even between different evaluations of
+  // the same policy request.
+  kVariableModeConst,
+
+  // Poll variables, or synchronous variables, represent a variable with a value
+  // that can be queried at any time, but it is not known when the value
+  // changes on the source of information. In order to detect if the value of
+  // the variable changes, it has to be queried again.
+  kVariableModePoll,
+
+  // Async variables are able to produce a signal or callback whenever the
+  // value changes. This means that it's not required to poll the value to
+  // detect when it changes, instead, you should register an observer to get
+  // a notification when that happens.
+  kVariableModeAsync,
+};
+
+// This class is a base class with the common functionality that doesn't
+// depend on the variable's type, implemented by all the variables.
+class BaseVariable {
+ public:
+  // Interface for observing changes on variable value.
+  class ObserverInterface {
+   public:
+    virtual ~ObserverInterface() {}
+
+    // Called when the value on the variable changes.
+    virtual void ValueChanged(BaseVariable* variable) = 0;
+  };
+
+  virtual ~BaseVariable() {
+    if (!observer_list_.empty()) {
+      LOG(WARNING) << "Variable " << name_ << " deleted with "
+                   << observer_list_.size() << " observers.";
+    }
+    DCHECK(observer_list_.empty()) << "Don't destroy the variable without "
+                                      "removing the observers.";
+  }
+
+  // Returns the variable name as a string.
+  const std::string& GetName() const {
+    return name_;
+  }
+
+  // Returns the variable mode.
+  VariableMode GetMode() const {
+    return mode_;
+  }
+
+  // For VariableModePoll variables, it returns the polling interval of this
+  // variable. In other case, it returns 0.
+  base::TimeDelta GetPollInterval() const {
+    return poll_interval_;
+  }
+
+  // Adds and removes observers for value changes on the variable. This only
+  // works for kVariableAsync variables since the other modes don't track value
+  // changes. Adding the same observer twice has no effect.
+  virtual void AddObserver(BaseVariable::ObserverInterface* observer) {
+    if (std::find(observer_list_.begin(), observer_list_.end(), observer) ==
+        observer_list_.end()) {
+      observer_list_.push_back(observer);
+    }
+  }
+
+  virtual void RemoveObserver(BaseVariable::ObserverInterface* observer) {
+    observer_list_.remove(observer);
+  }
+
+ protected:
+  // Creates a BaseVariable using the default polling interval (5 minutes).
+  BaseVariable(const std::string& name, VariableMode mode)
+      : BaseVariable(name, mode,
+                     base::TimeDelta::FromMinutes(kDefaultPollMinutes)) {}
+
+  // Creates a BaseVariable with mode kVariableModePoll and the provided
+  // polling interval.
+  BaseVariable(const std::string& name, base::TimeDelta poll_interval)
+      : BaseVariable(name, kVariableModePoll, poll_interval) {}
+
+  // Calls ValueChanged on all the observers.
+  void NotifyValueChanged() {
+    // Fire all the observer methods from the main loop as single call. In order
+    // to avoid scheduling these callbacks when it is not needed, we check
+    // first the list of observers.
+    if (!observer_list_.empty()) {
+      chromeos::MessageLoop::current()->PostTask(
+          FROM_HERE,
+          base::Bind(&BaseVariable::OnValueChangedNotification,
+                     base::Unretained(this)));
+    }
+  }
+
+ private:
+  friend class UmEvaluationContextTest;
+  FRIEND_TEST(UmBaseVariableTest, RepeatedObserverTest);
+  FRIEND_TEST(UmBaseVariableTest, NotifyValueChangedTest);
+  FRIEND_TEST(UmBaseVariableTest, NotifyValueRemovesObserversTest);
+
+  BaseVariable(const std::string& name, VariableMode mode,
+               base::TimeDelta poll_interval)
+    : name_(name), mode_(mode),
+      poll_interval_(mode == kVariableModePoll ?
+                     poll_interval : base::TimeDelta()) {}
+
+  void OnValueChangedNotification() {
+    // A ValueChanged() method can change the list of observers, for example
+    // removing itself and invalidating the iterator, so we create a snapshot
+    // of the observers first. Also, to support the case when *another* observer
+    // is removed, we check for them.
+    std::list<BaseVariable::ObserverInterface*> observer_list_copy(
+        observer_list_);
+
+    for (auto& observer : observer_list_copy) {
+      if (std::find(observer_list_.begin(), observer_list_.end(), observer) !=
+          observer_list_.end()) {
+        observer->ValueChanged(this);
+      }
+    }
+  }
+
+  // The default PollInterval in minutes.
+  static constexpr int kDefaultPollMinutes = 5;
+
+  // The variable's name as a string.
+  const std::string name_;
+
+  // The variable's mode.
+  const VariableMode mode_;
+
+  // The variable's polling interval for VariableModePoll variable and 0 for
+  // other modes.
+  const base::TimeDelta poll_interval_;
+
+  // The list of value changes observers.
+  std::list<BaseVariable::ObserverInterface*> observer_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(BaseVariable);
+};
+
+// Interface to an Update Manager variable of a given type. Implementation
+// internals are hidden as protected members, since policies should not be
+// using them directly.
+template<typename T>
+class Variable : public BaseVariable {
+ public:
+  ~Variable() override {}
+
+ protected:
+  // Only allow to get values through the EvaluationContext class and not
+  // directly from the variable.
+  friend class EvaluationContext;
+
+  // Needed to be able to verify variable contents during unit testing.
+  friend class UmTestUtils;
+  FRIEND_TEST(UmRealRandomProviderTest, GetRandomValues);
+
+  Variable(const std::string& name, VariableMode mode)
+      : BaseVariable(name, mode) {}
+
+  Variable(const std::string& name, const base::TimeDelta poll_interval)
+      : BaseVariable(name, poll_interval) {}
+
+  // Gets the current value of the variable. The current value is copied to a
+  // new object and returned. The caller of this method owns the object and
+  // should delete it.
+  //
+  // In case of and error getting the current value or the |timeout| timeout is
+  // exceeded, a null value is returned and the |errmsg| is set.
+  //
+  // The caller can pass a null value for |errmsg|, in which case the error
+  // message won't be set.
+  virtual const T* GetValue(base::TimeDelta timeout, std::string* errmsg) = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Variable);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
diff --git a/update_manager/variable_unittest.cc b/update_manager/variable_unittest.cc
new file mode 100644
index 0000000..000176b
--- /dev/null
+++ b/update_manager/variable_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright (c) 2014 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/update_manager/variable.h"
+
+#include <vector>
+
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+using base::TimeDelta;
+using chromeos::MessageLoop;
+using chromeos::MessageLoopRunMaxIterations;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+// Variable class that returns a value constructed with the default value.
+template <typename T>
+class DefaultVariable : public Variable<T> {
+ public:
+  DefaultVariable(const string& name, VariableMode mode)
+      : Variable<T>(name, mode) {}
+  DefaultVariable(const string& name, const TimeDelta& poll_interval)
+      : Variable<T>(name, poll_interval) {}
+  ~DefaultVariable() override {}
+
+ protected:
+  const T* GetValue(TimeDelta /* timeout */,
+                    string* /* errmsg */) override {
+    return new T();
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DefaultVariable);
+};
+
+class UmBaseVariableTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    loop_.SetAsCurrent();
+  }
+
+  chromeos::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmBaseVariableTest, GetNameTest) {
+  DefaultVariable<int> var("var", kVariableModeConst);
+  EXPECT_EQ(var.GetName(), string("var"));
+}
+
+TEST_F(UmBaseVariableTest, GetModeTest) {
+  DefaultVariable<int> var("var", kVariableModeConst);
+  EXPECT_EQ(var.GetMode(), kVariableModeConst);
+  DefaultVariable<int> other_var("other_var", kVariableModePoll);
+  EXPECT_EQ(other_var.GetMode(), kVariableModePoll);
+}
+
+TEST_F(UmBaseVariableTest, DefaultPollIntervalTest) {
+  DefaultVariable<int> const_var("const_var", kVariableModeConst);
+  EXPECT_EQ(const_var.GetPollInterval(), TimeDelta());
+  DefaultVariable<int> poll_var("poll_var", kVariableModePoll);
+  EXPECT_EQ(poll_var.GetPollInterval(), TimeDelta::FromMinutes(5));
+}
+
+TEST_F(UmBaseVariableTest, GetPollIntervalTest) {
+  DefaultVariable<int> var("var", TimeDelta::FromMinutes(3));
+  EXPECT_EQ(var.GetMode(), kVariableModePoll);
+  EXPECT_EQ(var.GetPollInterval(), TimeDelta::FromMinutes(3));
+}
+
+class BaseVariableObserver : public BaseVariable::ObserverInterface {
+ public:
+  void ValueChanged(BaseVariable* variable) {
+    calls_.push_back(variable);
+  }
+
+  // List of called functions.
+  vector<BaseVariable*> calls_;
+};
+
+TEST_F(UmBaseVariableTest, RepeatedObserverTest) {
+  DefaultVariable<int> var("var", kVariableModeAsync);
+  BaseVariableObserver observer;
+  var.AddObserver(&observer);
+  EXPECT_EQ(var.observer_list_.size(), 1);
+  var.AddObserver(&observer);
+  EXPECT_EQ(var.observer_list_.size(), 1);
+  var.RemoveObserver(&observer);
+  EXPECT_EQ(var.observer_list_.size(), 0);
+  var.RemoveObserver(&observer);
+  EXPECT_EQ(var.observer_list_.size(), 0);
+}
+
+TEST_F(UmBaseVariableTest, NotifyValueChangedTest) {
+  DefaultVariable<int> var("var", kVariableModeAsync);
+  BaseVariableObserver observer1;
+  var.AddObserver(&observer1);
+  // Simulate a value change on the variable's implementation.
+  var.NotifyValueChanged();
+  ASSERT_EQ(0, observer1.calls_.size());
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+  ASSERT_EQ(1, observer1.calls_.size());
+  // Check that the observer is called with the right argument.
+  EXPECT_EQ(&var, observer1.calls_[0]);
+
+  BaseVariableObserver observer2;
+  var.AddObserver(&observer2);
+  var.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+  // Check that all the observers are called.
+  EXPECT_EQ(2, observer1.calls_.size());
+  EXPECT_EQ(1, observer2.calls_.size());
+
+  var.RemoveObserver(&observer1);
+  var.RemoveObserver(&observer2);
+}
+
+class BaseVariableObserverRemover : public BaseVariable::ObserverInterface {
+ public:
+  BaseVariableObserverRemover() : calls_(0) {}
+
+  void ValueChanged(BaseVariable* variable) override {
+    for (auto& observer : remove_observers_) {
+      variable->RemoveObserver(observer);
+    }
+    calls_++;
+  }
+
+  void OnCallRemoveObserver(BaseVariable::ObserverInterface* observer) {
+    remove_observers_.push_back(observer);
+  }
+
+  int get_calls() { return calls_; }
+
+ private:
+  vector<BaseVariable::ObserverInterface*> remove_observers_;
+  int calls_;
+};
+
+// Tests that we can remove an observer from a Variable on the ValueChanged()
+// call to that observer.
+TEST_F(UmBaseVariableTest, NotifyValueRemovesObserversTest) {
+  DefaultVariable<int> var("var", kVariableModeAsync);
+  BaseVariableObserverRemover observer1;
+  BaseVariableObserverRemover observer2;
+
+  var.AddObserver(&observer1);
+  var.AddObserver(&observer2);
+
+  // Make each observer remove both observers on ValueChanged.
+  observer1.OnCallRemoveObserver(&observer1);
+  observer1.OnCallRemoveObserver(&observer2);
+  observer2.OnCallRemoveObserver(&observer1);
+  observer2.OnCallRemoveObserver(&observer2);
+
+  var.NotifyValueChanged();
+  MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+  EXPECT_EQ(1, observer1.get_calls() + observer2.get_calls());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_metadata.proto b/update_metadata.proto
new file mode 100644
index 0000000..bf0cb45
--- /dev/null
+++ b/update_metadata.proto
@@ -0,0 +1,177 @@
+// Copyright (c) 2010 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.
+
+// Update file format: A delta update file contains all the deltas needed
+// to update a system from one specific version to another specific
+// version. The update format is represented by this struct pseudocode:
+// struct delta_update_file {
+//   char magic[4] = "CrAU";
+//   uint64 file_format_version = 1;
+//   uint64 manifest_size;  // Size of protobuf DeltaArchiveManifest
+//   // The Bzip2 compressed DeltaArchiveManifest
+//   char manifest[];
+//
+//   // Data blobs for files, no specific format. The specific offset
+//   // and length of each data blob is recorded in the DeltaArchiveManifest.
+//   struct {
+//     char data[];
+//   } blobs[];
+//
+//   // These two are not signed:
+//   uint64 signatures_message_size;
+//   char signatures_message[];
+//
+// };
+
+// The DeltaArchiveManifest protobuf is an ordered list of InstallOperation
+// objects. These objects are stored in a linear array in the
+// DeltaArchiveManifest. Each operation is applied in order by the client.
+
+// The DeltaArchiveManifest also contains the initial and final
+// checksums for the device.
+
+// The client will perform each InstallOperation in order, beginning even
+// before the entire delta file is downloaded (but after at least the
+// protobuf is downloaded). The types of operations are explained:
+// - REPLACE: Replace the dst_extents on the drive with the attached data,
+//   zero padding out to block size.
+// - REPLACE_BZ: bzip2-uncompress the attached data and write it into
+//   dst_extents on the drive, zero padding to block size.
+// - MOVE: Copy the data in src_extents to dst_extents. Extents may overlap,
+//   so it may be desirable to read all src_extents data into memory before
+//   writing it out.
+// - BSDIFF: Read src_length bytes from src_extents into memory, perform
+//   bspatch with attached data, write new data to dst_extents, zero padding
+//   to block size.
+
+package chromeos_update_engine;
+option optimize_for = LITE_RUNTIME;
+
+// Data is packed into blocks on disk, always starting from the beginning
+// of the block. If a file's data is too large for one block, it overflows
+// into another block, which may or may not be the following block on the
+// physical partition. An ordered list of extents is another
+// representation of an ordered list of blocks. For example, a file stored
+// in blocks 9, 10, 11, 2, 18, 12 (in that order) would be stored in
+// extents { {9, 3}, {2, 1}, {18, 1}, {12, 1} } (in that order).
+// In general, files are stored sequentially on disk, so it's more efficient
+// to use extents to encode the block lists (this is effectively
+// run-length encoding).
+// A sentinel value (kuint64max) as the start block denotes a sparse-hole
+// in a file whose block-length is specified by num_blocks.
+
+// Signatures: Updates may be signed by the OS vendor. The client verifies
+// an update's signature by hashing the entire download. The section of the
+// download that contains the signature is at the end of the file, so when
+// signing a file, only the part up to the signature part is signed.
+// Then, the client looks inside the download's Signatures message for a
+// Signature message that it knows how to handle. Generally, a client will
+// only know how to handle one type of signature, but an update may contain
+// many signatures to support many different types of client. Then client
+// selects a Signature message and uses that, along with a known public key,
+// to verify the download. The public key is expected to be part of the
+// client.
+
+message Extent {
+  optional uint64 start_block = 1;
+  optional uint64 num_blocks = 2;
+}
+
+message Signatures {
+  message Signature {
+    optional uint32 version = 1;
+    optional bytes data = 2;
+  }
+  repeated Signature signatures = 1;
+}
+
+message PartitionInfo {
+  optional uint64 size = 1;
+  optional bytes hash = 2;
+}
+
+// Describe an image we are based on in a human friendly way.
+// Examples:
+//   dev-channel, x86-alex, 1.2.3, mp-v3
+//   nplusone-channel, x86-alex, 1.2.4, mp-v3, dev-channel, 1.2.3
+//
+// All fields will be set, if this message is present.
+message ImageInfo {
+  optional string board = 1;
+  optional string key = 2;
+  optional string channel = 3;
+  optional string version = 4;
+
+  // If these values aren't present, they should be assumed to match
+  // the equivalent value above. They are normally only different for
+  // special image types such as nplusone images.
+  optional string build_channel = 5;
+  optional string build_version = 6;
+}
+
+message DeltaArchiveManifest {
+  message InstallOperation {
+    enum Type {
+      REPLACE = 0;  // Replace destination extents w/ attached data
+      REPLACE_BZ = 1;  // Replace destination extents w/ attached bzipped data
+      MOVE = 2;  // Move source extents to destination extents
+      BSDIFF = 3;  // The data is a bsdiff binary diff
+      // SOURCE_COPY and SOURCE_BSDIFF are only supported on minor version 2.
+      SOURCE_COPY = 4; // Copy from source to target partition
+      SOURCE_BSDIFF = 5; // Like BSDIFF, but read from source partition
+    }
+    required Type type = 1;
+    // The offset into the delta file (after the protobuf)
+    // where the data (if any) is stored
+    optional uint32 data_offset = 2;
+    // The length of the data in the delta file
+    optional uint32 data_length = 3;
+
+    // Ordered list of extents that are read from (if any) and written to.
+    repeated Extent src_extents = 4;
+    // Byte length of src, equal to the number of blocks in src_extents *
+    // block_size. It is used for BSDIFF, because we need to pass that
+    // external program the number of bytes to read from the blocks we pass it.
+    // This is not used in any other operation.
+    optional uint64 src_length = 5;
+
+    repeated Extent dst_extents = 6;
+    // Byte length of dst, equal to the number of blocks in dst_extents *
+    // block_size. Used for BSDIFF, but not in any other operation.
+    optional uint64 dst_length = 7;
+
+    // Optional SHA 256 hash of the blob associated with this operation.
+    // This is used as a primary validation for http-based downloads and
+    // as a defense-in-depth validation for https-based downloads. If
+    // the operation doesn't refer to any blob, this field will have
+    // zero bytes.
+    optional bytes data_sha256_hash = 8;
+  }
+  repeated InstallOperation install_operations = 1;
+  repeated InstallOperation kernel_install_operations = 2;
+
+  // (At time of writing) usually 4096
+  optional uint32 block_size = 3 [default = 4096];
+
+  // If signatures are present, the offset into the blobs, generally
+  // tacked onto the end of the file, and the length. We use an offset
+  // rather than a bool to allow for more flexibility in future file formats.
+  // If either is absent, it means signatures aren't supported in this
+  // file.
+  optional uint64 signatures_offset = 4;
+  optional uint64 signatures_size = 5;
+
+  // Partition data that can be used to validate the update.
+  optional PartitionInfo old_kernel_info = 6;
+  optional PartitionInfo new_kernel_info = 7;
+  optional PartitionInfo old_rootfs_info = 8;
+  optional PartitionInfo new_rootfs_info = 9;
+
+  // old_image_info will only be present for delta images.
+  optional ImageInfo old_image_info = 10;
+
+  optional ImageInfo new_image_info = 11;
+
+  optional uint32 minor_version = 12 [default = 0];
+}
diff --git a/utils.cc b/utils.cc
new file mode 100644
index 0000000..97ce712
--- /dev/null
+++ b/utils.cc
@@ -0,0 +1,1671 @@
+// Copyright (c) 2012 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/utils.h"
+
+#include <stdint.h>
+
+#include <dirent.h>
+#include <elf.h>
+#include <errno.h>
+#include <ext2fs/ext2fs.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_file.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/data_encoding.h>
+#include <chromeos/message_loops/message_loop.h>
+
+#include "update_engine/clock_interface.h"
+#include "update_engine/constants.h"
+#include "update_engine/file_descriptor.h"
+#include "update_engine/file_writer.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/prefs_interface.h"
+#include "update_engine/subprocess.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_attempter.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The following constants control how UnmountFilesystem should retry if
+// umount() fails with an errno EBUSY, i.e. retry 5 times over the course of
+// one second.
+const int kUnmountMaxNumOfRetries = 5;
+const int kUnmountRetryIntervalInMicroseconds = 200 * 1000;  // 200 ms
+
+// Number of bytes to read from a file to attempt to detect its contents. Used
+// in GetFileFormat.
+const int kGetFileFormatMaxHeaderSize = 32;
+
+// Return true if |disk_name| is an MTD or a UBI device. Note that this test is
+// simply based on the name of the device.
+bool IsMtdDeviceName(const string& disk_name) {
+  return base::StartsWithASCII(disk_name, "/dev/ubi", true) ||
+         base::StartsWithASCII(disk_name, "/dev/mtd", true);
+}
+
+// Return the device name for the corresponding partition on a NAND device.
+// WARNING: This function returns device names that are not mountable.
+string MakeNandPartitionName(int partition_num) {
+  switch (partition_num) {
+    case 2:
+    case 4:
+    case 6: {
+      return base::StringPrintf("/dev/mtd%d", partition_num);
+    }
+    default: {
+      return base::StringPrintf("/dev/ubi%d_0", partition_num);
+    }
+  }
+}
+
+// Return the device name for the corresponding partition on a NAND device that
+// may be mountable (but may not be writable).
+string MakeNandPartitionNameForMount(int partition_num) {
+  switch (partition_num) {
+    case 2:
+    case 4:
+    case 6: {
+      return base::StringPrintf("/dev/mtd%d", partition_num);
+    }
+    case 3:
+    case 5:
+    case 7: {
+      return base::StringPrintf("/dev/ubiblock%d_0", partition_num);
+    }
+    default: {
+      return base::StringPrintf("/dev/ubi%d_0", partition_num);
+    }
+  }
+}
+
+}  // namespace
+
+namespace utils {
+
+// Cgroup container is created in update-engine's upstart script located at
+// /etc/init/update-engine.conf.
+static const char kCGroupDir[] = "/sys/fs/cgroup/cpu/update-engine";
+
+string ParseECVersion(string input_line) {
+  base::TrimWhitespaceASCII(input_line, base::TRIM_ALL, &input_line);
+
+  // At this point we want to convert the format key=value pair from mosys to
+  // a vector of key value pairs.
+  vector<pair<string, string>> kv_pairs;
+  if (base::SplitStringIntoKeyValuePairs(input_line, '=', ' ', &kv_pairs)) {
+    for (const pair<string, string>& kv_pair : kv_pairs) {
+      // Finally match against the fw_verion which may have quotes.
+      if (kv_pair.first == "fw_version") {
+        string output;
+        // Trim any quotes.
+        base::TrimString(kv_pair.second, "\"", &output);
+        return output;
+      }
+    }
+  }
+  LOG(ERROR) << "Unable to parse fwid from ec info.";
+  return "";
+}
+
+
+const string KernelDeviceOfBootDevice(const string& boot_device) {
+  string kernel_partition_name;
+
+  string disk_name;
+  int partition_num;
+  if (SplitPartitionName(boot_device, &disk_name, &partition_num)) {
+    // Currently this assumes the partition number of the boot device is
+    // 3, 5, or 7, and changes it to 2, 4, or 6, respectively, to
+    // get the kernel device.
+    if (partition_num == 3 || partition_num == 5 || partition_num == 7) {
+      kernel_partition_name = MakePartitionName(disk_name, partition_num - 1);
+    }
+  }
+
+  return kernel_partition_name;
+}
+
+
+bool WriteFile(const char* path, const void* data, int data_len) {
+  DirectFileWriter writer;
+  TEST_AND_RETURN_FALSE_ERRNO(0 == writer.Open(path,
+                                               O_WRONLY | O_CREAT | O_TRUNC,
+                                               0600));
+  ScopedFileWriterCloser closer(&writer);
+  TEST_AND_RETURN_FALSE_ERRNO(writer.Write(data, data_len));
+  return true;
+}
+
+bool WriteAll(int fd, const void* buf, size_t count) {
+  const char* c_buf = static_cast<const char*>(buf);
+  ssize_t bytes_written = 0;
+  while (bytes_written < static_cast<ssize_t>(count)) {
+    ssize_t rc = write(fd, c_buf + bytes_written, count - bytes_written);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    bytes_written += rc;
+  }
+  return true;
+}
+
+bool PWriteAll(int fd, const void* buf, size_t count, off_t offset) {
+  const char* c_buf = static_cast<const char*>(buf);
+  size_t bytes_written = 0;
+  int num_attempts = 0;
+  while (bytes_written < count) {
+    num_attempts++;
+    ssize_t rc = pwrite(fd, c_buf + bytes_written, count - bytes_written,
+                        offset + bytes_written);
+    // TODO(garnold) for debugging failure in chromium-os:31077; to be removed.
+    if (rc < 0) {
+      PLOG(ERROR) << "pwrite error; num_attempts=" << num_attempts
+                  << " bytes_written=" << bytes_written
+                  << " count=" << count << " offset=" << offset;
+    }
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    bytes_written += rc;
+  }
+  return true;
+}
+
+bool WriteAll(FileDescriptorPtr fd, const void* buf, size_t count) {
+  const char* c_buf = static_cast<const char*>(buf);
+  ssize_t bytes_written = 0;
+  while (bytes_written < static_cast<ssize_t>(count)) {
+    ssize_t rc = fd->Write(c_buf + bytes_written, count - bytes_written);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    bytes_written += rc;
+  }
+  return true;
+}
+
+bool PWriteAll(FileDescriptorPtr fd,
+               const void* buf,
+               size_t count,
+               off_t offset) {
+  TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) !=
+                              static_cast<off_t>(-1));
+  return WriteAll(fd, buf, count);
+}
+
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read) {
+  char* c_buf = static_cast<char*>(buf);
+  ssize_t bytes_read = 0;
+  while (bytes_read < static_cast<ssize_t>(count)) {
+    ssize_t rc = pread(fd, c_buf + bytes_read, count - bytes_read,
+                       offset + bytes_read);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    if (rc == 0) {
+      break;
+    }
+    bytes_read += rc;
+  }
+  *out_bytes_read = bytes_read;
+  return true;
+}
+
+bool PReadAll(FileDescriptorPtr fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read) {
+  TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) !=
+                              static_cast<off_t>(-1));
+  char* c_buf = static_cast<char*>(buf);
+  ssize_t bytes_read = 0;
+  while (bytes_read < static_cast<ssize_t>(count)) {
+    ssize_t rc = fd->Read(c_buf + bytes_read, count - bytes_read);
+    TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+    if (rc == 0) {
+      break;
+    }
+    bytes_read += rc;
+  }
+  *out_bytes_read = bytes_read;
+  return true;
+}
+
+// Append |nbytes| of content from |buf| to the vector pointed to by either
+// |vec_p| or |str_p|.
+static void AppendBytes(const uint8_t* buf, size_t nbytes,
+                        chromeos::Blob* vec_p) {
+  CHECK(buf);
+  CHECK(vec_p);
+  vec_p->insert(vec_p->end(), buf, buf + nbytes);
+}
+static void AppendBytes(const uint8_t* buf, size_t nbytes,
+                        string* str_p) {
+  CHECK(buf);
+  CHECK(str_p);
+  str_p->append(buf, buf + nbytes);
+}
+
+// Reads from an open file |fp|, appending the read content to the container
+// pointer to by |out_p|.  Returns true upon successful reading all of the
+// file's content, false otherwise. If |size| is not -1, reads up to |size|
+// bytes.
+template <class T>
+static bool Read(FILE* fp, off_t size, T* out_p) {
+  CHECK(fp);
+  CHECK(size == -1 || size >= 0);
+  uint8_t buf[1024];
+  while (size == -1 || size > 0) {
+    off_t bytes_to_read = sizeof(buf);
+    if (size > 0 && bytes_to_read > size) {
+      bytes_to_read = size;
+    }
+    size_t nbytes = fread(buf, 1, bytes_to_read, fp);
+    if (!nbytes) {
+      break;
+    }
+    AppendBytes(buf, nbytes, out_p);
+    if (size != -1) {
+      CHECK(size >= static_cast<off_t>(nbytes));
+      size -= nbytes;
+    }
+  }
+  if (ferror(fp)) {
+    return false;
+  }
+  return size == 0 || feof(fp);
+}
+
+// Opens a file |path| for reading and appends its the contents to a container
+// |out_p|. Starts reading the file from |offset|. If |offset| is beyond the end
+// of the file, returns success. If |size| is not -1, reads up to |size| bytes.
+template <class T>
+static bool ReadFileChunkAndAppend(const string& path,
+                                   off_t offset,
+                                   off_t size,
+                                   T* out_p) {
+  CHECK_GE(offset, 0);
+  CHECK(size == -1 || size >= 0);
+  base::ScopedFILE fp(fopen(path.c_str(), "r"));
+  if (!fp.get())
+    return false;
+  if (offset) {
+    // Return success without appending any data if a chunk beyond the end of
+    // the file is requested.
+    if (offset >= FileSize(path)) {
+      return true;
+    }
+    TEST_AND_RETURN_FALSE_ERRNO(fseek(fp.get(), offset, SEEK_SET) == 0);
+  }
+  return Read(fp.get(), size, out_p);
+}
+
+// TODO(deymo): This is only used in unittest, but requires the private
+// Read<string>() defined here. Expose Read<string>() or move to base/ version.
+bool ReadPipe(const string& cmd, string* out_p) {
+  FILE* fp = popen(cmd.c_str(), "r");
+  if (!fp)
+    return false;
+  bool success = Read(fp, -1, out_p);
+  return (success && pclose(fp) >= 0);
+}
+
+bool ReadFile(const string& path, chromeos::Blob* out_p) {
+  return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFile(const string& path, string* out_p) {
+  return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFileChunk(const string& path, off_t offset, off_t size,
+                   chromeos::Blob* out_p) {
+  return ReadFileChunkAndAppend(path, offset, size, out_p);
+}
+
+off_t BlockDevSize(int fd) {
+  uint64_t dev_size;
+  int rc = ioctl(fd, BLKGETSIZE64, &dev_size);
+  if (rc == -1) {
+    dev_size = -1;
+    PLOG(ERROR) << "Error running ioctl(BLKGETSIZE64) on " << fd;
+  }
+  return dev_size;
+}
+
+off_t BlockDevSize(const string& path) {
+  int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error opening " << path;
+    return fd;
+  }
+
+  off_t dev_size = BlockDevSize(fd);
+  if (dev_size == -1)
+    PLOG(ERROR) << "Error getting block device size on " << path;
+
+  close(fd);
+  return dev_size;
+}
+
+off_t FileSize(int fd) {
+  struct stat stbuf;
+  int rc = fstat(fd, &stbuf);
+  CHECK_EQ(rc, 0);
+  if (rc < 0) {
+    PLOG(ERROR) << "Error stat-ing " << fd;
+    return rc;
+  }
+  if (S_ISREG(stbuf.st_mode))
+    return stbuf.st_size;
+  if (S_ISBLK(stbuf.st_mode))
+    return BlockDevSize(fd);
+  LOG(ERROR) << "Couldn't determine the type of " << fd;
+  return -1;
+}
+
+off_t FileSize(const string& path) {
+  int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+  if (fd == -1) {
+    PLOG(ERROR) << "Error opening " << path;
+    return fd;
+  }
+  off_t size = FileSize(fd);
+  if (size == -1)
+    PLOG(ERROR) << "Error getting file size of " << path;
+  close(fd);
+  return size;
+}
+
+void HexDumpArray(const uint8_t* const arr, const size_t length) {
+  LOG(INFO) << "Logging array of length: " << length;
+  const unsigned int bytes_per_line = 16;
+  for (uint32_t i = 0; i < length; i += bytes_per_line) {
+    const unsigned int bytes_remaining = length - i;
+    const unsigned int bytes_per_this_line = min(bytes_per_line,
+                                                 bytes_remaining);
+    char header[100];
+    int r = snprintf(header, sizeof(header), "0x%08x : ", i);
+    TEST_AND_RETURN(r == 13);
+    string line = header;
+    for (unsigned int j = 0; j < bytes_per_this_line; j++) {
+      char buf[20];
+      uint8_t c = arr[i + j];
+      r = snprintf(buf, sizeof(buf), "%02x ", static_cast<unsigned int>(c));
+      TEST_AND_RETURN(r == 3);
+      line += buf;
+    }
+    LOG(INFO) << line;
+  }
+}
+
+string GetDiskName(const string& partition_name) {
+  string disk_name;
+  return SplitPartitionName(partition_name, &disk_name, nullptr) ?
+      disk_name : string();
+}
+
+int GetPartitionNumber(const string& partition_name) {
+  int partition_num = 0;
+  return SplitPartitionName(partition_name, nullptr, &partition_num) ?
+      partition_num : 0;
+}
+
+bool SplitPartitionName(const string& partition_name,
+                        string* out_disk_name,
+                        int* out_partition_num) {
+  if (!base::StartsWithASCII(partition_name, "/dev/", true)) {
+    LOG(ERROR) << "Invalid partition device name: " << partition_name;
+    return false;
+  }
+
+  size_t last_nondigit_pos = partition_name.find_last_not_of("0123456789");
+  if (last_nondigit_pos == string::npos ||
+      (last_nondigit_pos + 1) == partition_name.size()) {
+    LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
+    return false;
+  }
+
+  size_t partition_name_len = string::npos;
+  if (partition_name[last_nondigit_pos] == '_') {
+    // NAND block devices have weird naming which could be something
+    // like "/dev/ubiblock2_0". We discard "_0" in such a case.
+    size_t prev_nondigit_pos =
+        partition_name.find_last_not_of("0123456789", last_nondigit_pos - 1);
+    if (prev_nondigit_pos == string::npos ||
+        (prev_nondigit_pos + 1) == last_nondigit_pos) {
+      LOG(ERROR) << "Unable to parse partition device name: " << partition_name;
+      return false;
+    }
+
+    partition_name_len = last_nondigit_pos - prev_nondigit_pos;
+    last_nondigit_pos = prev_nondigit_pos;
+  }
+
+  if (out_disk_name) {
+    // Special case for MMC devices which have the following naming scheme:
+    // mmcblk0p2
+    size_t disk_name_len = last_nondigit_pos;
+    if (partition_name[last_nondigit_pos] != 'p' ||
+        last_nondigit_pos == 0 ||
+        !isdigit(partition_name[last_nondigit_pos - 1])) {
+      disk_name_len++;
+    }
+    *out_disk_name = partition_name.substr(0, disk_name_len);
+  }
+
+  if (out_partition_num) {
+    string partition_str = partition_name.substr(last_nondigit_pos + 1,
+                                                 partition_name_len);
+    *out_partition_num = atoi(partition_str.c_str());
+  }
+  return true;
+}
+
+string MakePartitionName(const string& disk_name, int partition_num) {
+  if (partition_num < 1) {
+    LOG(ERROR) << "Invalid partition number: " << partition_num;
+    return string();
+  }
+
+  if (!base::StartsWithASCII(disk_name, "/dev/", true)) {
+    LOG(ERROR) << "Invalid disk name: " << disk_name;
+    return string();
+  }
+
+  if (IsMtdDeviceName(disk_name)) {
+    // Special case for UBI block devices.
+    //   1. ubiblock is not writable, we need to use plain "ubi".
+    //   2. There is a "_0" suffix.
+    return MakeNandPartitionName(partition_num);
+  }
+
+  string partition_name = disk_name;
+  if (isdigit(partition_name.back())) {
+    // Special case for devices with names ending with a digit.
+    // Add "p" to separate the disk name from partition number,
+    // e.g. "/dev/loop0p2"
+    partition_name += 'p';
+  }
+
+  partition_name += std::to_string(partition_num);
+
+  return partition_name;
+}
+
+string MakePartitionNameForMount(const string& part_name) {
+  if (IsMtdDeviceName(part_name)) {
+    int partition_num;
+    if (!SplitPartitionName(part_name, nullptr, &partition_num)) {
+      return "";
+    }
+    return MakeNandPartitionNameForMount(partition_num);
+  }
+  return part_name;
+}
+
+string SysfsBlockDevice(const string& device) {
+  base::FilePath device_path(device);
+  if (device_path.DirName().value() != "/dev") {
+    return "";
+  }
+  return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
+}
+
+bool IsRemovableDevice(const string& device) {
+  string sysfs_block = SysfsBlockDevice(device);
+  string removable;
+  if (sysfs_block.empty() ||
+      !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
+                              &removable)) {
+    return false;
+  }
+  base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
+  return removable == "1";
+}
+
+string ErrnoNumberAsString(int err) {
+  char buf[100];
+  buf[0] = '\0';
+  return strerror_r(err, buf, sizeof(buf));
+}
+
+bool FileExists(const char* path) {
+  struct stat stbuf;
+  return 0 == lstat(path, &stbuf);
+}
+
+bool IsSymlink(const char* path) {
+  struct stat stbuf;
+  return lstat(path, &stbuf) == 0 && S_ISLNK(stbuf.st_mode) != 0;
+}
+
+bool TryAttachingUbiVolume(int volume_num, int timeout) {
+  const string volume_path = base::StringPrintf("/dev/ubi%d_0", volume_num);
+  if (FileExists(volume_path.c_str())) {
+    return true;
+  }
+
+  int exit_code;
+  vector<string> cmd = {
+      "ubiattach",
+      "-m",
+      base::StringPrintf("%d", volume_num),
+      "-d",
+      base::StringPrintf("%d", volume_num)
+  };
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr));
+  TEST_AND_RETURN_FALSE(exit_code == 0);
+
+  cmd = {
+      "ubiblock",
+      "--create",
+      volume_path
+  };
+  TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &exit_code, nullptr));
+  TEST_AND_RETURN_FALSE(exit_code == 0);
+
+  while (timeout > 0 && !FileExists(volume_path.c_str())) {
+    sleep(1);
+    timeout--;
+  }
+
+  return FileExists(volume_path.c_str());
+}
+
+// If |path| is absolute, or explicit relative to the current working directory,
+// leaves it as is. Otherwise, if TMPDIR is defined in the environment and is
+// non-empty, prepends it to |path|. Otherwise, prepends /tmp.  Returns the
+// resulting path.
+static const string PrependTmpdir(const string& path) {
+  if (path[0] == '/' || base::StartsWithASCII(path, "./", true) ||
+      base::StartsWithASCII(path, "../", true))
+    return path;
+
+  const char *tmpdir = getenv("TMPDIR");
+  const string prefix = (tmpdir && *tmpdir ? tmpdir : "/tmp");
+  return prefix + "/" + path;
+}
+
+bool MakeTempFile(const string& base_filename_template,
+                  string* filename,
+                  int* fd) {
+  const string filename_template = PrependTmpdir(base_filename_template);
+  DCHECK(filename || fd);
+  vector<char> buf(filename_template.size() + 1);
+  memcpy(buf.data(), filename_template.data(), filename_template.size());
+  buf[filename_template.size()] = '\0';
+
+  int mkstemp_fd = mkstemp(buf.data());
+  TEST_AND_RETURN_FALSE_ERRNO(mkstemp_fd >= 0);
+  if (filename) {
+    *filename = buf.data();
+  }
+  if (fd) {
+    *fd = mkstemp_fd;
+  } else {
+    close(mkstemp_fd);
+  }
+  return true;
+}
+
+bool MakeTempDirectory(const string& base_dirname_template,
+                       string* dirname) {
+  const string dirname_template = PrependTmpdir(base_dirname_template);
+  DCHECK(dirname);
+  vector<char> buf(dirname_template.size() + 1);
+  memcpy(buf.data(), dirname_template.data(), dirname_template.size());
+  buf[dirname_template.size()] = '\0';
+
+  char* return_code = mkdtemp(buf.data());
+  TEST_AND_RETURN_FALSE_ERRNO(return_code != nullptr);
+  *dirname = buf.data();
+  return true;
+}
+
+bool MountFilesystem(const string& device,
+                     const string& mountpoint,
+                     unsigned long mountflags) {  // NOLINT(runtime/int)
+  // TODO(sosa): Remove "ext3" once crbug.com/208022 is resolved.
+  const vector<const char*> fstypes{"ext2", "ext3", "squashfs"};
+  for (const char* fstype : fstypes) {
+    int rc = mount(device.c_str(), mountpoint.c_str(), fstype, mountflags,
+                   nullptr);
+    if (rc == 0)
+      return true;
+
+    PLOG(WARNING) << "Unable to mount destination device " << device
+                  << " on " << mountpoint << " as " << fstype;
+  }
+  LOG(ERROR) << "Unable to mount " << device << " with any supported type";
+  return false;
+}
+
+bool UnmountFilesystem(const string& mountpoint) {
+  for (int num_retries = 0; ; ++num_retries) {
+    if (umount(mountpoint.c_str()) == 0)
+      break;
+
+    TEST_AND_RETURN_FALSE_ERRNO(errno == EBUSY &&
+                                num_retries < kUnmountMaxNumOfRetries);
+    usleep(kUnmountRetryIntervalInMicroseconds);
+  }
+  return true;
+}
+
+bool GetFilesystemSize(const string& device,
+                       int* out_block_count,
+                       int* out_block_size) {
+  int fd = HANDLE_EINTR(open(device.c_str(), O_RDONLY));
+  TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+  ScopedFdCloser fd_closer(&fd);
+  return GetFilesystemSizeFromFD(fd, out_block_count, out_block_size);
+}
+
+bool GetFilesystemSizeFromFD(int fd,
+                             int* out_block_count,
+                             int* out_block_size) {
+  TEST_AND_RETURN_FALSE(fd >= 0);
+
+  // Determine the filesystem size by directly reading the block count and
+  // block size information from the superblock. Supported FS are ext3 and
+  // squashfs.
+
+  // Read from the fd only once and detect in memory. The first 2 KiB is enough
+  // to read the ext2 superblock (located at offset 1024) and the squashfs
+  // superblock (located at offset 0).
+  const ssize_t kBufferSize = 2048;
+
+  uint8_t buffer[kBufferSize];
+  if (HANDLE_EINTR(pread(fd, buffer, kBufferSize, 0)) != kBufferSize) {
+    PLOG(ERROR) << "Unable to read the file system header:";
+    return false;
+  }
+
+  if (GetSquashfs4Size(buffer, kBufferSize, out_block_count, out_block_size))
+    return true;
+  if (GetExt3Size(buffer, kBufferSize, out_block_count, out_block_size))
+    return true;
+
+  LOG(ERROR) << "Unable to determine file system type.";
+  return false;
+}
+
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+                 int* out_block_count,
+                 int* out_block_size) {
+  // See include/linux/ext2_fs.h for more details on the structure. We obtain
+  // ext2 constants from ext2fs/ext2fs.h header but we don't link with the
+  // library.
+  if (buffer_size < SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE)
+    return false;
+
+  const uint8_t* superblock = buffer + SUPERBLOCK_OFFSET;
+
+  // ext3_fs.h: ext3_super_block.s_blocks_count
+  uint32_t block_count =
+      *reinterpret_cast<const uint32_t*>(superblock + 1 * sizeof(int32_t));
+
+  // ext3_fs.h: ext3_super_block.s_log_block_size
+  uint32_t log_block_size =
+      *reinterpret_cast<const uint32_t*>(superblock + 6 * sizeof(int32_t));
+
+  // ext3_fs.h: ext3_super_block.s_magic
+  uint16_t magic =
+      *reinterpret_cast<const uint16_t*>(superblock + 14 * sizeof(int32_t));
+
+  block_count = le32toh(block_count);
+  log_block_size = le32toh(log_block_size) + EXT2_MIN_BLOCK_LOG_SIZE;
+  magic = le16toh(magic);
+
+  // Sanity check the parameters.
+  TEST_AND_RETURN_FALSE(magic == EXT2_SUPER_MAGIC);
+  TEST_AND_RETURN_FALSE(log_block_size >= EXT2_MIN_BLOCK_LOG_SIZE &&
+                        log_block_size <= EXT2_MAX_BLOCK_LOG_SIZE);
+  TEST_AND_RETURN_FALSE(block_count > 0);
+
+  if (out_block_count)
+    *out_block_count = block_count;
+  if (out_block_size)
+    *out_block_size = 1 << log_block_size;
+  return true;
+}
+
+bool GetSquashfs4Size(const uint8_t* buffer, size_t buffer_size,
+                      int* out_block_count,
+                      int* out_block_size) {
+  // See fs/squashfs/squashfs_fs.h for format details. We only support
+  // Squashfs 4.x little endian.
+
+  // sizeof(struct squashfs_super_block)
+  const size_t kSquashfsSuperBlockSize = 96;
+  if (buffer_size < kSquashfsSuperBlockSize)
+    return false;
+
+  // Check magic, squashfs_fs.h: SQUASHFS_MAGIC
+  if (memcmp(buffer, "hsqs", 4) != 0)
+    return false;  // Only little endian is supported.
+
+  // squashfs_fs.h: struct squashfs_super_block.s_major
+  uint16_t s_major = *reinterpret_cast<const uint16_t*>(
+      buffer + 5 * sizeof(uint32_t) + 4 * sizeof(uint16_t));
+
+  if (s_major != 4) {
+    LOG(ERROR) << "Found unsupported squashfs major version " << s_major;
+    return false;
+  }
+
+  // squashfs_fs.h: struct squashfs_super_block.bytes_used
+  uint64_t bytes_used = *reinterpret_cast<const int64_t*>(
+      buffer + 5 * sizeof(uint32_t) + 6 * sizeof(uint16_t) + sizeof(uint64_t));
+
+  const int block_size = 4096;
+
+  // The squashfs' bytes_used doesn't need to be aligned with the block boundary
+  // so we round up to the nearest blocksize.
+  if (out_block_count)
+    *out_block_count = (bytes_used + block_size - 1) / block_size;
+  if (out_block_size)
+    *out_block_size = block_size;
+  return true;
+}
+
+bool IsExtFilesystem(const string& device) {
+  chromeos::Blob header;
+  // The first 2 KiB is enough to read the ext2 superblock (located at offset
+  // 1024).
+  if (!ReadFileChunk(device, 0, 2048, &header))
+    return false;
+  return GetExt3Size(header.data(), header.size(), nullptr, nullptr);
+}
+
+bool IsSquashfsFilesystem(const string& device) {
+  chromeos::Blob header;
+  // The first 96 is enough to read the squashfs superblock.
+  const ssize_t kSquashfsSuperBlockSize = 96;
+  if (!ReadFileChunk(device, 0, kSquashfsSuperBlockSize, &header))
+    return false;
+  return GetSquashfs4Size(header.data(), header.size(), nullptr, nullptr);
+}
+
+// Tries to parse the header of an ELF file to obtain a human-readable
+// description of it on the |output| string.
+static bool GetFileFormatELF(const uint8_t* buffer, size_t size,
+                             string* output) {
+  // 0x00: EI_MAG - ELF magic header, 4 bytes.
+  if (size < SELFMAG || memcmp(buffer, ELFMAG, SELFMAG) != 0)
+    return false;
+  *output = "ELF";
+
+  // 0x04: EI_CLASS, 1 byte.
+  if (size < EI_CLASS + 1)
+    return true;
+  switch (buffer[EI_CLASS]) {
+    case ELFCLASS32:
+      *output += " 32-bit";
+      break;
+    case ELFCLASS64:
+      *output += " 64-bit";
+      break;
+    default:
+      *output += " ?-bit";
+  }
+
+  // 0x05: EI_DATA, endianness, 1 byte.
+  if (size < EI_DATA + 1)
+    return true;
+  uint8_t ei_data = buffer[EI_DATA];
+  switch (ei_data) {
+    case ELFDATA2LSB:
+      *output += " little-endian";
+      break;
+    case ELFDATA2MSB:
+      *output += " big-endian";
+      break;
+    default:
+      *output += " ?-endian";
+      // Don't parse anything after the 0x10 offset if endianness is unknown.
+      return true;
+  }
+
+  const Elf32_Ehdr* hdr = reinterpret_cast<const Elf32_Ehdr*>(buffer);
+  // 0x12: e_machine, 2 byte endianness based on ei_data. The position (0x12)
+  // and size is the same for both 32 and 64 bits.
+  if (size < offsetof(Elf32_Ehdr, e_machine) + sizeof(hdr->e_machine))
+    return true;
+  uint16_t e_machine;
+  // Fix endianess regardless of the host endianess.
+  if (ei_data == ELFDATA2LSB)
+    e_machine = le16toh(hdr->e_machine);
+  else
+    e_machine = be16toh(hdr->e_machine);
+
+  switch (e_machine) {
+    case EM_386:
+      *output += " x86";
+      break;
+    case EM_MIPS:
+      *output += " mips";
+      break;
+    case EM_ARM:
+      *output += " arm";
+      break;
+    case EM_X86_64:
+      *output += " x86-64";
+      break;
+    default:
+      *output += " unknown-arch";
+  }
+  return true;
+}
+
+string GetFileFormat(const string& path) {
+  chromeos::Blob buffer;
+  if (!ReadFileChunkAndAppend(path, 0, kGetFileFormatMaxHeaderSize, &buffer))
+    return "File not found.";
+
+  string result;
+  if (GetFileFormatELF(buffer.data(), buffer.size(), &result))
+    return result;
+
+  return "data";
+}
+
+namespace {
+// Do the actual trigger. We do it as a main-loop callback to (try to) get a
+// consistent stack trace.
+void TriggerCrashReporterUpload() {
+  pid_t pid = fork();
+  CHECK_GE(pid, 0) << "fork failed";  // fork() failed. Something is very wrong.
+  if (pid == 0) {
+    // We are the child. Crash.
+    abort();  // never returns
+  }
+  // We are the parent. Wait for child to terminate.
+  pid_t result = waitpid(pid, nullptr, 0);
+  LOG_IF(ERROR, result < 0) << "waitpid() failed";
+}
+}  // namespace
+
+void ScheduleCrashReporterUpload() {
+  chromeos::MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(&TriggerCrashReporterUpload));
+}
+
+bool SetCpuShares(CpuShares shares) {
+  string string_shares = base::IntToString(static_cast<int>(shares));
+  string cpu_shares_file = string(utils::kCGroupDir) + "/cpu.shares";
+  LOG(INFO) << "Setting cgroup cpu shares to  " << string_shares;
+  if (utils::WriteFile(cpu_shares_file.c_str(), string_shares.c_str(),
+                       string_shares.size())) {
+    return true;
+  } else {
+    LOG(ERROR) << "Failed to change cgroup cpu shares to "<< string_shares
+               << " using " << cpu_shares_file;
+    return false;
+  }
+}
+
+int FuzzInt(int value, unsigned int range) {
+  int min = value - range / 2;
+  int max = value + range - range / 2;
+  return base::RandInt(min, max);
+}
+
+string FormatSecs(unsigned secs) {
+  return FormatTimeDelta(TimeDelta::FromSeconds(secs));
+}
+
+string FormatTimeDelta(TimeDelta delta) {
+  string str;
+
+  // Handle negative durations by prefixing with a minus.
+  if (delta.ToInternalValue() < 0) {
+    delta *= -1;
+    str = "-";
+  }
+
+  // Canonicalize into days, hours, minutes, seconds and microseconds.
+  unsigned days = delta.InDays();
+  delta -= TimeDelta::FromDays(days);
+  unsigned hours = delta.InHours();
+  delta -= TimeDelta::FromHours(hours);
+  unsigned mins = delta.InMinutes();
+  delta -= TimeDelta::FromMinutes(mins);
+  unsigned secs = delta.InSeconds();
+  delta -= TimeDelta::FromSeconds(secs);
+  unsigned usecs = delta.InMicroseconds();
+
+  if (days)
+    base::StringAppendF(&str, "%ud", days);
+  if (days || hours)
+    base::StringAppendF(&str, "%uh", hours);
+  if (days || hours || mins)
+    base::StringAppendF(&str, "%um", mins);
+  base::StringAppendF(&str, "%u", secs);
+  if (usecs) {
+    int width = 6;
+    while ((usecs / 10) * 10 == usecs) {
+      usecs /= 10;
+      width--;
+    }
+    base::StringAppendF(&str, ".%0*u", width, usecs);
+  }
+  base::StringAppendF(&str, "s");
+  return str;
+}
+
+string ToString(const Time utc_time) {
+  Time::Exploded exp_time;
+  utc_time.UTCExplode(&exp_time);
+  return base::StringPrintf("%d/%d/%d %d:%02d:%02d GMT",
+                      exp_time.month,
+                      exp_time.day_of_month,
+                      exp_time.year,
+                      exp_time.hour,
+                      exp_time.minute,
+                      exp_time.second);
+}
+
+string ToString(bool b) {
+  return (b ? "true" : "false");
+}
+
+string ToString(DownloadSource source) {
+  switch (source) {
+    case kDownloadSourceHttpsServer: return "HttpsServer";
+    case kDownloadSourceHttpServer:  return "HttpServer";
+    case kDownloadSourceHttpPeer:    return "HttpPeer";
+    case kNumDownloadSources:        return "Unknown";
+    // Don't add a default case to let the compiler warn about newly added
+    // download sources which should be added here.
+  }
+
+  return "Unknown";
+}
+
+string ToString(PayloadType payload_type) {
+  switch (payload_type) {
+    case kPayloadTypeDelta:      return "Delta";
+    case kPayloadTypeFull:       return "Full";
+    case kPayloadTypeForcedFull: return "ForcedFull";
+    case kNumPayloadTypes:       return "Unknown";
+    // Don't add a default case to let the compiler warn about newly added
+    // payload types which should be added here.
+  }
+
+  return "Unknown";
+}
+
+ErrorCode GetBaseErrorCode(ErrorCode code) {
+  // Ignore the higher order bits in the code by applying the mask as
+  // we want the enumerations to be in the small contiguous range
+  // with values less than ErrorCode::kUmaReportedMax.
+  ErrorCode base_code = static_cast<ErrorCode>(
+      static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+  // Make additional adjustments required for UMA and error classification.
+  // TODO(jaysri): Move this logic to UeErrorCode.cc when we fix
+  // chromium-os:34369.
+  if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+    // Since we want to keep the enums to a small value, aggregate all HTTP
+    // errors into this one bucket for UMA and error classification purposes.
+    LOG(INFO) << "Converting error code " << base_code
+              << " to ErrorCode::kOmahaErrorInHTTPResponse";
+    base_code = ErrorCode::kOmahaErrorInHTTPResponse;
+  }
+
+  return base_code;
+}
+
+metrics::AttemptResult GetAttemptResult(ErrorCode code) {
+  ErrorCode base_code = static_cast<ErrorCode>(
+      static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+  switch (base_code) {
+    case ErrorCode::kSuccess:
+      return metrics::AttemptResult::kUpdateSucceeded;
+
+    case ErrorCode::kDownloadTransferError:
+      return metrics::AttemptResult::kPayloadDownloadError;
+
+    case ErrorCode::kDownloadInvalidMetadataSize:
+    case ErrorCode::kDownloadInvalidMetadataMagicString:
+    case ErrorCode::kDownloadMetadataSignatureError:
+    case ErrorCode::kDownloadMetadataSignatureVerificationError:
+    case ErrorCode::kPayloadMismatchedType:
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kDownloadNewPartitionInfoError:
+    case ErrorCode::kDownloadSignatureMissingInManifest:
+    case ErrorCode::kDownloadManifestParseError:
+    case ErrorCode::kDownloadOperationHashMissingError:
+      return metrics::AttemptResult::kMetadataMalformed;
+
+    case ErrorCode::kDownloadOperationHashMismatch:
+    case ErrorCode::kDownloadOperationHashVerificationError:
+      return metrics::AttemptResult::kOperationMalformed;
+
+    case ErrorCode::kDownloadOperationExecutionError:
+    case ErrorCode::kInstallDeviceOpenError:
+    case ErrorCode::kKernelDeviceOpenError:
+    case ErrorCode::kDownloadWriteError:
+    case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kFilesystemVerifierError:
+      return metrics::AttemptResult::kOperationExecutionError;
+
+    case ErrorCode::kDownloadMetadataSignatureMismatch:
+      return metrics::AttemptResult::kMetadataVerificationFailed;
+
+    case ErrorCode::kPayloadSizeMismatchError:
+    case ErrorCode::kPayloadHashMismatchError:
+    case ErrorCode::kDownloadPayloadVerificationError:
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+      return metrics::AttemptResult::kPayloadVerificationFailed;
+
+    case ErrorCode::kNewRootfsVerificationError:
+    case ErrorCode::kNewKernelVerificationError:
+      return metrics::AttemptResult::kVerificationFailed;
+
+    case ErrorCode::kPostinstallRunnerError:
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+      return metrics::AttemptResult::kPostInstallFailed;
+
+    // We should never get these errors in the update-attempt stage so
+    // return internal error if this happens.
+    case ErrorCode::kError:
+    case ErrorCode::kOmahaRequestXMLParseError:
+    case ErrorCode::kOmahaRequestError:
+    case ErrorCode::kOmahaResponseHandlerError:
+    case ErrorCode::kDownloadStateInitializationError:
+    case ErrorCode::kOmahaRequestEmptyResponseError:
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+    case ErrorCode::kOmahaResponseInvalid:
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kOmahaErrorInHTTPResponse:
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+    case ErrorCode::kPostinstallPowerwashError:
+    case ErrorCode::kUpdateCanceledByChannelChange:
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+      return metrics::AttemptResult::kInternalError;
+
+    // Special flags. These can't happen (we mask them out above) but
+    // the compiler doesn't know that. Just break out so we can warn and
+    // return |kInternalError|.
+    case ErrorCode::kUmaReportedMax:
+    case ErrorCode::kOmahaRequestHTTPResponseBase:
+    case ErrorCode::kDevModeFlag:
+    case ErrorCode::kResumedFlag:
+    case ErrorCode::kTestImageFlag:
+    case ErrorCode::kTestOmahaUrlFlag:
+    case ErrorCode::kSpecialFlags:
+      break;
+  }
+
+  LOG(ERROR) << "Unexpected error code " << base_code;
+  return metrics::AttemptResult::kInternalError;
+}
+
+
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) {
+  ErrorCode base_code = static_cast<ErrorCode>(
+      static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+  if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+    int http_status =
+        static_cast<int>(base_code) -
+        static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase);
+    if (http_status >= 200 && http_status <= 599) {
+      return static_cast<metrics::DownloadErrorCode>(
+          static_cast<int>(metrics::DownloadErrorCode::kHttpStatus200) +
+          http_status - 200);
+    } else if (http_status == 0) {
+      // The code is using HTTP Status 0 for "Unable to get http
+      // response code."
+      return metrics::DownloadErrorCode::kDownloadError;
+    }
+    LOG(WARNING) << "Unexpected HTTP status code " << http_status;
+    return metrics::DownloadErrorCode::kHttpStatusOther;
+  }
+
+  switch (base_code) {
+    // Unfortunately, ErrorCode::kDownloadTransferError is returned for a wide
+    // variety of errors (proxy errors, host not reachable, timeouts etc.).
+    //
+    // For now just map that to kDownloading. See http://crbug.com/355745
+    // for how we plan to add more detail in the future.
+    case ErrorCode::kDownloadTransferError:
+      return metrics::DownloadErrorCode::kDownloadError;
+
+    // All of these error codes are not related to downloading so break
+    // out so we can warn and return InputMalformed.
+    case ErrorCode::kSuccess:
+    case ErrorCode::kError:
+    case ErrorCode::kOmahaRequestError:
+    case ErrorCode::kOmahaResponseHandlerError:
+    case ErrorCode::kFilesystemCopierError:
+    case ErrorCode::kPostinstallRunnerError:
+    case ErrorCode::kPayloadMismatchedType:
+    case ErrorCode::kInstallDeviceOpenError:
+    case ErrorCode::kKernelDeviceOpenError:
+    case ErrorCode::kPayloadHashMismatchError:
+    case ErrorCode::kPayloadSizeMismatchError:
+    case ErrorCode::kDownloadPayloadVerificationError:
+    case ErrorCode::kDownloadNewPartitionInfoError:
+    case ErrorCode::kDownloadWriteError:
+    case ErrorCode::kNewRootfsVerificationError:
+    case ErrorCode::kNewKernelVerificationError:
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+    case ErrorCode::kDownloadStateInitializationError:
+    case ErrorCode::kDownloadInvalidMetadataMagicString:
+    case ErrorCode::kDownloadSignatureMissingInManifest:
+    case ErrorCode::kDownloadManifestParseError:
+    case ErrorCode::kDownloadMetadataSignatureError:
+    case ErrorCode::kDownloadMetadataSignatureVerificationError:
+    case ErrorCode::kDownloadMetadataSignatureMismatch:
+    case ErrorCode::kDownloadOperationHashVerificationError:
+    case ErrorCode::kDownloadOperationExecutionError:
+    case ErrorCode::kDownloadOperationHashMismatch:
+    case ErrorCode::kOmahaRequestEmptyResponseError:
+    case ErrorCode::kOmahaRequestXMLParseError:
+    case ErrorCode::kDownloadInvalidMetadataSize:
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+    case ErrorCode::kOmahaResponseInvalid:
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kOmahaErrorInHTTPResponse:
+    case ErrorCode::kDownloadOperationHashMissingError:
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+    case ErrorCode::kPostinstallPowerwashError:
+    case ErrorCode::kUpdateCanceledByChannelChange:
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kFilesystemVerifierError:
+      break;
+
+    // Special flags. These can't happen (we mask them out above) but
+    // the compiler doesn't know that. Just break out so we can warn and
+    // return |kInputMalformed|.
+    case ErrorCode::kUmaReportedMax:
+    case ErrorCode::kOmahaRequestHTTPResponseBase:
+    case ErrorCode::kDevModeFlag:
+    case ErrorCode::kResumedFlag:
+    case ErrorCode::kTestImageFlag:
+    case ErrorCode::kTestOmahaUrlFlag:
+    case ErrorCode::kSpecialFlags:
+      LOG(ERROR) << "Unexpected error code " << base_code;
+      break;
+  }
+
+  return metrics::DownloadErrorCode::kInputMalformed;
+}
+
+metrics::ConnectionType GetConnectionType(
+    NetworkConnectionType type,
+    NetworkTethering tethering) {
+  switch (type) {
+    case kNetUnknown:
+      return metrics::ConnectionType::kUnknown;
+
+    case kNetEthernet:
+      if (tethering == NetworkTethering::kConfirmed)
+        return metrics::ConnectionType::kTetheredEthernet;
+      else
+        return metrics::ConnectionType::kEthernet;
+
+    case kNetWifi:
+      if (tethering == NetworkTethering::kConfirmed)
+        return metrics::ConnectionType::kTetheredWifi;
+      else
+        return metrics::ConnectionType::kWifi;
+
+    case kNetWimax:
+      return metrics::ConnectionType::kWimax;
+
+    case kNetBluetooth:
+      return metrics::ConnectionType::kBluetooth;
+
+    case kNetCellular:
+      return metrics::ConnectionType::kCellular;
+  }
+
+  LOG(ERROR) << "Unexpected network connection type: type=" << type
+             << ", tethering=" << static_cast<int>(tethering);
+
+  return metrics::ConnectionType::kUnknown;
+}
+
+// Returns a printable version of the various flags denoted in the higher order
+// bits of the given code. Returns an empty string if none of those bits are
+// set.
+string GetFlagNames(uint32_t code) {
+  uint32_t flags = (static_cast<uint32_t>(code) &
+                    static_cast<uint32_t>(ErrorCode::kSpecialFlags));
+  string flag_names;
+  string separator = "";
+  for (size_t i = 0; i < sizeof(flags) * 8; i++) {
+    uint32_t flag = flags & (1 << i);
+    if (flag) {
+      flag_names += separator + CodeToString(static_cast<ErrorCode>(flag));
+      separator = ", ";
+    }
+  }
+
+  return flag_names;
+}
+
+void SendErrorCodeToUma(SystemState* system_state, ErrorCode code) {
+  if (!system_state)
+    return;
+
+  ErrorCode uma_error_code = GetBaseErrorCode(code);
+
+  // If the code doesn't have flags computed already, compute them now based on
+  // the state of the current update attempt.
+  uint32_t flags =
+      static_cast<int>(code) & static_cast<int>(ErrorCode::kSpecialFlags);
+  if (!flags)
+    flags = system_state->update_attempter()->GetErrorCodeFlags();
+
+  // Determine the UMA bucket depending on the flags. But, ignore the resumed
+  // flag, as it's perfectly normal for production devices to resume their
+  // downloads and so we want to record those cases also in NormalErrorCodes
+  // bucket.
+  string metric =
+      flags & ~static_cast<uint32_t>(ErrorCode::kResumedFlag) ?
+      "Installer.DevModeErrorCodes" : "Installer.NormalErrorCodes";
+
+  LOG(INFO) << "Sending error code " << uma_error_code
+            << " (" << CodeToString(uma_error_code) << ")"
+            << " to UMA metric: " << metric
+            << ". Flags = " << (flags ? GetFlagNames(flags) : "None");
+
+  system_state->metrics_lib()->SendEnumToUMA(
+      metric, static_cast<int>(uma_error_code),
+      static_cast<int>(ErrorCode::kUmaReportedMax));
+}
+
+string CodeToString(ErrorCode code) {
+  // If the given code has both parts (i.e. the error code part and the flags
+  // part) then strip off the flags part since the switch statement below
+  // has case statements only for the base error code or a single flag but
+  // doesn't support any combinations of those.
+  if ((static_cast<int>(code) & static_cast<int>(ErrorCode::kSpecialFlags)) &&
+      (static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags)))
+    code = static_cast<ErrorCode>(
+        static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+  switch (code) {
+    case ErrorCode::kSuccess: return "ErrorCode::kSuccess";
+    case ErrorCode::kError: return "ErrorCode::kError";
+    case ErrorCode::kOmahaRequestError: return "ErrorCode::kOmahaRequestError";
+    case ErrorCode::kOmahaResponseHandlerError:
+      return "ErrorCode::kOmahaResponseHandlerError";
+    case ErrorCode::kFilesystemCopierError:
+      return "ErrorCode::kFilesystemCopierError";
+    case ErrorCode::kPostinstallRunnerError:
+      return "ErrorCode::kPostinstallRunnerError";
+    case ErrorCode::kPayloadMismatchedType:
+      return "ErrorCode::kPayloadMismatchedType";
+    case ErrorCode::kInstallDeviceOpenError:
+      return "ErrorCode::kInstallDeviceOpenError";
+    case ErrorCode::kKernelDeviceOpenError:
+      return "ErrorCode::kKernelDeviceOpenError";
+    case ErrorCode::kDownloadTransferError:
+      return "ErrorCode::kDownloadTransferError";
+    case ErrorCode::kPayloadHashMismatchError:
+      return "ErrorCode::kPayloadHashMismatchError";
+    case ErrorCode::kPayloadSizeMismatchError:
+      return "ErrorCode::kPayloadSizeMismatchError";
+    case ErrorCode::kDownloadPayloadVerificationError:
+      return "ErrorCode::kDownloadPayloadVerificationError";
+    case ErrorCode::kDownloadNewPartitionInfoError:
+      return "ErrorCode::kDownloadNewPartitionInfoError";
+    case ErrorCode::kDownloadWriteError:
+      return "ErrorCode::kDownloadWriteError";
+    case ErrorCode::kNewRootfsVerificationError:
+      return "ErrorCode::kNewRootfsVerificationError";
+    case ErrorCode::kNewKernelVerificationError:
+      return "ErrorCode::kNewKernelVerificationError";
+    case ErrorCode::kSignedDeltaPayloadExpectedError:
+      return "ErrorCode::kSignedDeltaPayloadExpectedError";
+    case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+      return "ErrorCode::kDownloadPayloadPubKeyVerificationError";
+    case ErrorCode::kPostinstallBootedFromFirmwareB:
+      return "ErrorCode::kPostinstallBootedFromFirmwareB";
+    case ErrorCode::kDownloadStateInitializationError:
+      return "ErrorCode::kDownloadStateInitializationError";
+    case ErrorCode::kDownloadInvalidMetadataMagicString:
+      return "ErrorCode::kDownloadInvalidMetadataMagicString";
+    case ErrorCode::kDownloadSignatureMissingInManifest:
+      return "ErrorCode::kDownloadSignatureMissingInManifest";
+    case ErrorCode::kDownloadManifestParseError:
+      return "ErrorCode::kDownloadManifestParseError";
+    case ErrorCode::kDownloadMetadataSignatureError:
+      return "ErrorCode::kDownloadMetadataSignatureError";
+    case ErrorCode::kDownloadMetadataSignatureVerificationError:
+      return "ErrorCode::kDownloadMetadataSignatureVerificationError";
+    case ErrorCode::kDownloadMetadataSignatureMismatch:
+      return "ErrorCode::kDownloadMetadataSignatureMismatch";
+    case ErrorCode::kDownloadOperationHashVerificationError:
+      return "ErrorCode::kDownloadOperationHashVerificationError";
+    case ErrorCode::kDownloadOperationExecutionError:
+      return "ErrorCode::kDownloadOperationExecutionError";
+    case ErrorCode::kDownloadOperationHashMismatch:
+      return "ErrorCode::kDownloadOperationHashMismatch";
+    case ErrorCode::kOmahaRequestEmptyResponseError:
+      return "ErrorCode::kOmahaRequestEmptyResponseError";
+    case ErrorCode::kOmahaRequestXMLParseError:
+      return "ErrorCode::kOmahaRequestXMLParseError";
+    case ErrorCode::kDownloadInvalidMetadataSize:
+      return "ErrorCode::kDownloadInvalidMetadataSize";
+    case ErrorCode::kDownloadInvalidMetadataSignature:
+      return "ErrorCode::kDownloadInvalidMetadataSignature";
+    case ErrorCode::kOmahaResponseInvalid:
+      return "ErrorCode::kOmahaResponseInvalid";
+    case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+      return "ErrorCode::kOmahaUpdateIgnoredPerPolicy";
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+      return "ErrorCode::kOmahaUpdateDeferredPerPolicy";
+    case ErrorCode::kOmahaErrorInHTTPResponse:
+      return "ErrorCode::kOmahaErrorInHTTPResponse";
+    case ErrorCode::kDownloadOperationHashMissingError:
+      return "ErrorCode::kDownloadOperationHashMissingError";
+    case ErrorCode::kDownloadMetadataSignatureMissingError:
+      return "ErrorCode::kDownloadMetadataSignatureMissingError";
+    case ErrorCode::kOmahaUpdateDeferredForBackoff:
+      return "ErrorCode::kOmahaUpdateDeferredForBackoff";
+    case ErrorCode::kPostinstallPowerwashError:
+      return "ErrorCode::kPostinstallPowerwashError";
+    case ErrorCode::kUpdateCanceledByChannelChange:
+      return "ErrorCode::kUpdateCanceledByChannelChange";
+    case ErrorCode::kUmaReportedMax:
+      return "ErrorCode::kUmaReportedMax";
+    case ErrorCode::kOmahaRequestHTTPResponseBase:
+      return "ErrorCode::kOmahaRequestHTTPResponseBase";
+    case ErrorCode::kResumedFlag:
+      return "Resumed";
+    case ErrorCode::kDevModeFlag:
+      return "DevMode";
+    case ErrorCode::kTestImageFlag:
+      return "TestImage";
+    case ErrorCode::kTestOmahaUrlFlag:
+      return "TestOmahaUrl";
+    case ErrorCode::kSpecialFlags:
+      return "ErrorCode::kSpecialFlags";
+    case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+      return "ErrorCode::kPostinstallFirmwareRONotUpdatable";
+    case ErrorCode::kUnsupportedMajorPayloadVersion:
+      return "ErrorCode::kUnsupportedMajorPayloadVersion";
+    case ErrorCode::kUnsupportedMinorPayloadVersion:
+      return "ErrorCode::kUnsupportedMinorPayloadVersion";
+    case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+      return "ErrorCode::kOmahaRequestXMLHasEntityDecl";
+    case ErrorCode::kFilesystemVerifierError:
+      return "ErrorCode::kFilesystemVerifierError";
+    // Don't add a default case to let the compiler warn about newly added
+    // error codes which should be added here.
+  }
+
+  return "Unknown error: " + base::UintToString(static_cast<unsigned>(code));
+}
+
+bool CreatePowerwashMarkerFile(const char* file_path) {
+  const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
+  bool result = utils::WriteFile(marker_file,
+                                 kPowerwashCommand,
+                                 strlen(kPowerwashCommand));
+  if (result) {
+    LOG(INFO) << "Created " << marker_file << " to powerwash on next reboot";
+  } else {
+    PLOG(ERROR) << "Error in creating powerwash marker file: " << marker_file;
+  }
+
+  return result;
+}
+
+bool DeletePowerwashMarkerFile(const char* file_path) {
+  const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
+  const base::FilePath kPowerwashMarkerPath(marker_file);
+  bool result = base::DeleteFile(kPowerwashMarkerPath, false);
+
+  if (result)
+    LOG(INFO) << "Successfully deleted the powerwash marker file : "
+              << marker_file;
+  else
+    PLOG(ERROR) << "Could not delete the powerwash marker file : "
+                << marker_file;
+
+  return result;
+}
+
+bool GetInstallDev(const string& boot_dev, string* install_dev) {
+  string disk_name;
+  int partition_num;
+  if (!SplitPartitionName(boot_dev, &disk_name, &partition_num))
+    return false;
+
+  // Right now, we just switch '3' and '5' partition numbers.
+  if (partition_num == 3) {
+    partition_num = 5;
+  } else if (partition_num == 5) {
+    partition_num = 3;
+  } else {
+    return false;
+  }
+
+  if (install_dev)
+    *install_dev = MakePartitionName(disk_name, partition_num);
+
+  return true;
+}
+
+Time TimeFromStructTimespec(struct timespec *ts) {
+  int64_t us = static_cast<int64_t>(ts->tv_sec) * Time::kMicrosecondsPerSecond +
+      static_cast<int64_t>(ts->tv_nsec) / Time::kNanosecondsPerMicrosecond;
+  return Time::UnixEpoch() + TimeDelta::FromMicroseconds(us);
+}
+
+string StringVectorToString(const vector<string> &vec_str) {
+  string str = "[";
+  for (vector<string>::const_iterator i = vec_str.begin();
+       i != vec_str.end(); ++i) {
+    if (i != vec_str.begin())
+      str += ", ";
+    str += '"';
+    str += *i;
+    str += '"';
+  }
+  str += "]";
+  return str;
+}
+
+string CalculateP2PFileId(const string& payload_hash, size_t payload_size) {
+  string encoded_hash = chromeos::data_encoding::Base64Encode(payload_hash);
+  return base::StringPrintf("cros_update_size_%zu_hash_%s",
+                            payload_size,
+                            encoded_hash.c_str());
+}
+
+bool DecodeAndStoreBase64String(const string& base64_encoded,
+                                base::FilePath *out_path) {
+  chromeos::Blob contents;
+
+  out_path->clear();
+
+  if (base64_encoded.size() == 0) {
+    LOG(ERROR) << "Can't decode empty string.";
+    return false;
+  }
+
+  if (!chromeos::data_encoding::Base64Decode(base64_encoded, &contents) ||
+      contents.size() == 0) {
+    LOG(ERROR) << "Error decoding base64.";
+    return false;
+  }
+
+  FILE *file = base::CreateAndOpenTemporaryFile(out_path);
+  if (file == nullptr) {
+    LOG(ERROR) << "Error creating temporary file.";
+    return false;
+  }
+
+  if (fwrite(contents.data(), 1, contents.size(), file) != contents.size()) {
+    PLOG(ERROR) << "Error writing to temporary file.";
+    if (fclose(file) != 0)
+      PLOG(ERROR) << "Error closing temporary file.";
+    if (unlink(out_path->value().c_str()) != 0)
+      PLOG(ERROR) << "Error unlinking temporary file.";
+    out_path->clear();
+    return false;
+  }
+
+  if (fclose(file) != 0) {
+    PLOG(ERROR) << "Error closing temporary file.";
+    out_path->clear();
+    return false;
+  }
+
+  return true;
+}
+
+bool ConvertToOmahaInstallDate(Time time, int *out_num_days) {
+  time_t unix_time = time.ToTimeT();
+  // Output of: date +"%s" --date="Jan 1, 2007 0:00 PST".
+  const time_t kOmahaEpoch = 1167638400;
+  const int64_t kNumSecondsPerWeek = 7*24*3600;
+  const int64_t kNumDaysPerWeek = 7;
+
+  time_t omaha_time = unix_time - kOmahaEpoch;
+
+  if (omaha_time < 0)
+    return false;
+
+  // Note, as per the comment in utils.h we are deliberately not
+  // handling DST correctly.
+
+  int64_t num_weeks_since_omaha_epoch = omaha_time / kNumSecondsPerWeek;
+  *out_num_days = num_weeks_since_omaha_epoch * kNumDaysPerWeek;
+
+  return true;
+}
+
+bool WallclockDurationHelper(SystemState* system_state,
+                             const string& state_variable_key,
+                             TimeDelta* out_duration) {
+  bool ret = false;
+
+  Time now = system_state->clock()->GetWallclockTime();
+  int64_t stored_value;
+  if (system_state->prefs()->GetInt64(state_variable_key, &stored_value)) {
+    Time stored_time = Time::FromInternalValue(stored_value);
+    if (stored_time > now) {
+      LOG(ERROR) << "Stored time-stamp used for " << state_variable_key
+                 << " is in the future.";
+    } else {
+      *out_duration = now - stored_time;
+      ret = true;
+    }
+  }
+
+  if (!system_state->prefs()->SetInt64(state_variable_key,
+                                       now.ToInternalValue())) {
+    LOG(ERROR) << "Error storing time-stamp in " << state_variable_key;
+  }
+
+  return ret;
+}
+
+bool MonotonicDurationHelper(SystemState* system_state,
+                             int64_t* storage,
+                             TimeDelta* out_duration) {
+  bool ret = false;
+
+  Time now = system_state->clock()->GetMonotonicTime();
+  if (*storage != 0) {
+    Time stored_time = Time::FromInternalValue(*storage);
+    *out_duration = now - stored_time;
+    ret = true;
+  }
+  *storage = now.ToInternalValue();
+
+  return ret;
+}
+
+bool GetMinorVersion(const chromeos::KeyValueStore& store,
+                     uint32_t* minor_version) {
+  string result;
+  if (store.GetString("PAYLOAD_MINOR_VERSION", &result)) {
+    if (!base::StringToUint(result, minor_version)) {
+      LOG(ERROR) << "StringToUint failed when parsing delta minor version.";
+      return false;
+    }
+    return true;
+  }
+  return false;
+}
+
+bool ReadExtents(const string& path, const vector<Extent>& extents,
+                 chromeos::Blob* out_data, ssize_t out_data_size,
+                 size_t block_size) {
+  chromeos::Blob data(out_data_size);
+  ssize_t bytes_read = 0;
+  int fd = open(path.c_str(), O_RDONLY);
+  TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+  ScopedFdCloser fd_closer(&fd);
+
+  for (const Extent& extent : extents) {
+    ssize_t bytes_read_this_iteration = 0;
+    ssize_t bytes = extent.num_blocks() * block_size;
+    TEST_AND_RETURN_FALSE(bytes_read + bytes <= out_data_size);
+    TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
+                                          &data[bytes_read],
+                                          bytes,
+                                          extent.start_block() * block_size,
+                                          &bytes_read_this_iteration));
+    TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes);
+    bytes_read += bytes_read_this_iteration;
+  }
+  TEST_AND_RETURN_FALSE(out_data_size == bytes_read);
+  *out_data = data;
+  return true;
+}
+
+}  // namespace utils
+
+}  // namespace chromeos_update_engine
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..b661b43
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,611 @@
+// Copyright (c) 2012 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_UTILS_H_
+#define UPDATE_ENGINE_UTILS_H_
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/time/time.h>
+#include <chromeos/secure_blob.h>
+#include <chromeos/key_value_store.h>
+#include "metrics/metrics_library.h"
+
+#include "update_engine/action.h"
+#include "update_engine/action_processor.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/constants.h"
+#include "update_engine/file_descriptor.h"
+#include "update_engine/metrics.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace utils {
+
+// Converts a struct timespec representing a number of seconds since
+// the Unix epoch to a base::Time. Sub-microsecond time is rounded
+// down.
+base::Time TimeFromStructTimespec(struct timespec *ts);
+
+// Formats |vec_str| as a string of the form ["<elem1>", "<elem2>"].
+// Does no escaping, only use this for presentation in error messages.
+std::string StringVectorToString(const std::vector<std::string> &vec_str);
+
+// Calculates the p2p file id from payload hash and size
+std::string CalculateP2PFileId(const std::string& payload_hash,
+                               size_t payload_size);
+
+// Parse the firmware version from one line of output from the
+// "mosys" command.
+std::string ParseECVersion(std::string input_line);
+
+// Given the name of the block device of a boot partition, return the
+// name of the associated kernel partition (e.g. given "/dev/sda3",
+// return "/dev/sda2").
+const std::string KernelDeviceOfBootDevice(const std::string& boot_device);
+
+// Writes the data passed to path. The file at path will be overwritten if it
+// exists. Returns true on success, false otherwise.
+bool WriteFile(const char* path, const void* data, int data_len);
+
+// Calls write() or pwrite() repeatedly until all count bytes at buf are
+// written to fd or an error occurs. Returns true on success.
+bool WriteAll(int fd, const void* buf, size_t count);
+bool PWriteAll(int fd, const void* buf, size_t count, off_t offset);
+
+bool WriteAll(FileDescriptorPtr fd, const void* buf, size_t count);
+bool PWriteAll(FileDescriptorPtr fd,
+               const void* buf,
+               size_t count,
+               off_t offset);
+
+// Calls pread() repeatedly until count bytes are read, or EOF is reached.
+// Returns number of bytes read in *bytes_read. Returns true on success.
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read);
+
+bool PReadAll(FileDescriptorPtr fd, void* buf, size_t count, off_t offset,
+              ssize_t* out_bytes_read);
+
+// Opens |path| for reading and appends its entire content to the container
+// pointed to by |out_p|. Returns true upon successfully reading all of the
+// file's content, false otherwise, in which case the state of the output
+// container is unknown. ReadFileChunk starts reading the file from |offset|; if
+// |size| is not -1, only up to |size| bytes are read in.
+bool ReadFile(const std::string& path, chromeos::Blob* out_p);
+bool ReadFile(const std::string& path, std::string* out_p);
+bool ReadFileChunk(const std::string& path, off_t offset, off_t size,
+                   chromeos::Blob* out_p);
+
+// Invokes |cmd| in a pipe and appends its stdout to the container pointed to by
+// |out_p|. Returns true upon successfully reading all of the output, false
+// otherwise, in which case the state of the output container is unknown.
+bool ReadPipe(const std::string& cmd, std::string* out_p);
+
+// Returns the size of the block device at path, or the file descriptor fd. If
+// an error occurs, -1 is returned.
+off_t BlockDevSize(const std::string& path);
+off_t BlockDevSize(int fd);
+
+// Returns the size of the file at path, or the file desciptor fd. If the file
+// is actually a block device, this function will automatically call
+// BlockDevSize. If the file doesn't exist or some error occurrs, -1 is
+// returned.
+off_t FileSize(const std::string& path);
+off_t FileSize(int fd);
+
+std::string ErrnoNumberAsString(int err);
+
+// Returns true if the file exists for sure. Returns false if it doesn't exist,
+// or an error occurs.
+bool FileExists(const char* path);
+
+// Returns true if |path| exists and is a symbolic link.
+bool IsSymlink(const char* path);
+
+// Try attaching UBI |volume_num|. If there is any error executing required
+// commands to attach the volume, this function returns false. This function
+// only returns true if "/dev/ubi%d_0" becomes available in |timeout| seconds.
+bool TryAttachingUbiVolume(int volume_num, int timeout);
+
+// If |base_filename_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the value of TMPDIR, which defaults to /tmp if
+// it isn't set or is empty.  It then calls mkstemp(3) with the resulting
+// template.  Writes the name of a new temporary file to |filename|. If |fd| is
+// non-null, the file descriptor returned by mkstemp is written to it and
+// kept open; otherwise, it is closed. The template must end with "XXXXXX".
+// Returns true on success.
+bool MakeTempFile(const std::string& base_filename_template,
+                  std::string* filename,
+                  int* fd);
+
+// If |base_filename_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the value of TMPDIR, which defaults to /tmp if
+// it isn't set or is empty.  It then calls mkdtemp() with the resulting
+// template. Writes the name of the new temporary directory to |dirname|.
+// The template must end with "XXXXXX". Returns true on success.
+bool MakeTempDirectory(const std::string& base_dirname_template,
+                       std::string* dirname);
+
+// Returns the disk device name for a partition. For example,
+// GetDiskName("/dev/sda3") returns "/dev/sda". Returns an empty string
+// if the input device is not of the "/dev/xyz#" form.
+std::string GetDiskName(const std::string& partition_name);
+
+// Returns the partition number, of partition device name. For example,
+// GetPartitionNumber("/dev/sda3") returns 3.
+// Returns 0 on failure
+int GetPartitionNumber(const std::string& partition_name);
+
+// Splits the partition device name into the block device name and partition
+// number. For example, "/dev/sda3" will be split into {"/dev/sda", 3} and
+// "/dev/mmcblk0p2" into {"/dev/mmcblk0", 2}
+// Returns false when malformed device name is passed in.
+// If both output parameters are omitted (null), can be used
+// just to test the validity of the device name. Note that the function
+// simply checks if the device name looks like a valid device, no other
+// checks are performed (i.e. it doesn't check if the device actually exists).
+bool SplitPartitionName(const std::string& partition_name,
+                        std::string* out_disk_name,
+                        int* out_partition_num);
+
+// Builds a partition device name from the block device name and partition
+// number. For example:
+// {"/dev/sda", 1} => "/dev/sda1"
+// {"/dev/mmcblk2", 12} => "/dev/mmcblk2p12"
+// Returns empty string when invalid parameters are passed in
+std::string MakePartitionName(const std::string& disk_name,
+                              int partition_num);
+
+// Similar to "MakePartitionName" but returns a name that is suitable for
+// mounting. On NAND system we can write to "/dev/ubiX_0", which is what
+// MakePartitionName returns, but we cannot mount that device. To mount, we
+// have to use "/dev/ubiblockX_0" for rootfs. Stateful and OEM partitions are
+// mountable with "/dev/ubiX_0". The input is a partition device such as
+// /dev/sda3. Return empty string on error.
+std::string MakePartitionNameForMount(const std::string& part_name);
+
+// Returns the sysfs block device for a root block device. For
+// example, SysfsBlockDevice("/dev/sda") returns
+// "/sys/block/sda". Returns an empty string if the input device is
+// not of the "/dev/xyz" form.
+std::string SysfsBlockDevice(const std::string& device);
+
+// Returns true if the root |device| (e.g., "/dev/sdb") is known to be
+// removable, false otherwise.
+bool IsRemovableDevice(const std::string& device);
+
+// Synchronously mount or unmount a filesystem. Return true on success.
+// When mounting, it will attempt to mount the the device as "ext3", "ext2" and
+// "squashfs", with the passed |flags| options.
+bool MountFilesystem(const std::string& device, const std::string& mountpoint,
+                     unsigned long flags);  // NOLINT(runtime/int)
+bool UnmountFilesystem(const std::string& mountpoint);
+
+// Returns the block count and the block byte size of the file system on
+// |device| (which may be a real device or a path to a filesystem image) or on
+// an opened file descriptor |fd|. The actual file-system size is |block_count|
+// * |block_size| bytes. Returns true on success, false otherwise.
+bool GetFilesystemSize(const std::string& device,
+                       int* out_block_count,
+                       int* out_block_size);
+bool GetFilesystemSizeFromFD(int fd,
+                             int* out_block_count,
+                             int* out_block_size);
+
+// Determines the block count and block size of the ext3 fs. At least 2048 bytes
+// are required to parse the first superblock. Returns whether the buffer
+// contains a valid ext3 filesystem and the values were parsed.
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+                 int* out_block_count,
+                 int* out_block_size);
+
+// Determines the block count and block size of the squashfs v4 fs. At least 96
+// bytes are required to parse the header of the filesystem. Since squashfs
+// doesn't define a physical block size, a value of 4096 is used for the block
+// size, which is the default padding when creating the filesystem.
+// Returns whether the buffer contains a valid squashfs v4 header and the size
+// was parsed. Only little endian squashfs is supported.
+bool GetSquashfs4Size(const uint8_t* buffer, size_t buffer_size,
+                      int* out_block_count,
+                      int* out_block_size);
+
+// Returns whether the filesystem is an ext[234] filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsExtFilesystem(const std::string& device);
+
+// Returns whether the filesystem is a squashfs filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsSquashfsFilesystem(const std::string& device);
+
+// Returns a human-readable string with the file format based on magic constants
+// on the header of the file.
+std::string GetFileFormat(const std::string& path);
+
+// Returns the string representation of the given UTC time.
+// such as "11/14/2011 14:05:30 GMT".
+std::string ToString(const base::Time utc_time);
+
+// Returns true or false depending on the value of b.
+std::string ToString(bool b);
+
+// Returns a string representation of the given enum.
+std::string ToString(DownloadSource source);
+
+// Returns a string representation of the given enum.
+std::string ToString(PayloadType payload_type);
+
+// Schedules a Main Loop callback to trigger the crash reporter to perform an
+// upload as if this process had crashed.
+void ScheduleCrashReporterUpload();
+
+// Fuzzes an integer |value| randomly in the range:
+// [value - range / 2, value + range - range / 2]
+int FuzzInt(int value, unsigned int range);
+
+// Log a string in hex to LOG(INFO). Useful for debugging.
+void HexDumpArray(const uint8_t* const arr, const size_t length);
+inline void HexDumpString(const std::string& str) {
+  HexDumpArray(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+inline void HexDumpVector(const chromeos::Blob& vect) {
+  HexDumpArray(vect.data(), vect.size());
+}
+
+template<typename KeyType, typename ValueType>
+bool MapContainsKey(const std::map<KeyType, ValueType>& m, const KeyType& k) {
+  return m.find(k) != m.end();
+}
+template<typename KeyType>
+bool SetContainsKey(const std::set<KeyType>& s, const KeyType& k) {
+  return s.find(k) != s.end();
+}
+
+template<typename T>
+bool VectorContainsValue(const std::vector<T>& vect, const T& value) {
+  return std::find(vect.begin(), vect.end(), value) != vect.end();
+}
+
+template<typename T>
+bool VectorIndexOf(const std::vector<T>& vect, const T& value,
+                   typename std::vector<T>::size_type* out_index) {
+  typename std::vector<T>::const_iterator it = std::find(vect.begin(),
+                                                         vect.end(),
+                                                         value);
+  if (it == vect.end()) {
+    return false;
+  } else {
+    *out_index = it - vect.begin();
+    return true;
+  }
+}
+
+// Cgroups cpu shares constants. 1024 is the default shares a standard process
+// gets and 2 is the minimum value. We set High as a value that gives the
+// update-engine 2x the cpu share of a standard process.
+enum CpuShares {
+  kCpuSharesHigh = 2048,
+  kCpuSharesNormal = 1024,
+  kCpuSharesLow = 2,
+};
+
+// Sets the current process shares to |shares|. Returns true on
+// success, false otherwise.
+bool SetCpuShares(CpuShares shares);
+
+// Converts seconds into human readable notation including days, hours, minutes
+// and seconds. For example, 185 will yield 3m5s, 4300 will yield 1h11m40s, and
+// 360000 will yield 4d4h0m0s.  Zero padding not applied. Seconds are always
+// shown in the result.
+std::string FormatSecs(unsigned secs);
+
+// Converts a TimeDelta into human readable notation including days, hours,
+// minutes, seconds and fractions of a second down to microsecond granularity,
+// as necessary; for example, an output of 5d2h0m15.053s means that the input
+// time was precise to the milliseconds only. Zero padding not applied, except
+// for fractions. Seconds are always shown, but fractions thereof are only shown
+// when applicable. If |delta| is negative, the output will have a leading '-'
+// followed by the absolute duration.
+std::string FormatTimeDelta(base::TimeDelta delta);
+
+// This method transforms the given error code to be suitable for UMA and
+// for error classification purposes by removing the higher order bits and
+// aggregating error codes beyond the enum range, etc. This method is
+// idempotent, i.e. if called with a value previously returned by this method,
+// it'll return the same value again.
+ErrorCode GetBaseErrorCode(ErrorCode code);
+
+// Transforms a ErrorCode value into a metrics::DownloadErrorCode.
+// This obviously only works for errors related to downloading so if |code|
+// is e.g. |ErrorCode::kFilesystemCopierError| then
+// |kDownloadErrorCodeInputMalformed| is returned.
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code);
+
+// Transforms a ErrorCode value into a metrics::AttemptResult.
+//
+// If metrics::AttemptResult::kPayloadDownloadError is returned, you
+// can use utils::GetDownloadError() to get more detail.
+metrics::AttemptResult GetAttemptResult(ErrorCode code);
+
+// Calculates the internet connection type given |type| and |tethering|.
+metrics::ConnectionType GetConnectionType(NetworkConnectionType type,
+                                          NetworkTethering tethering);
+
+// Sends the error code to UMA using the metrics interface object in the given
+// system state. It also uses the system state to determine the right UMA
+// bucket for the error code.
+void SendErrorCodeToUma(SystemState* system_state, ErrorCode code);
+
+// Returns a string representation of the ErrorCodes (either the base
+// error codes or the bit flags) for logging purposes.
+std::string CodeToString(ErrorCode code);
+
+// Creates the powerwash marker file with the appropriate commands in it.  Uses
+// |file_path| as the path to the marker file if non-null, otherwise uses the
+// global default. Returns true if successfully created.  False otherwise.
+bool CreatePowerwashMarkerFile(const char* file_path);
+
+// Deletes the marker file used to trigger Powerwash using clobber-state.  Uses
+// |file_path| as the path to the marker file if non-null, otherwise uses the
+// global default. Returns true if successfully deleted. False otherwise.
+bool DeletePowerwashMarkerFile(const char* file_path);
+
+// Assumes you want to install on the "other" device, where the other
+// device is what you get if you swap 1 for 2 or 3 for 4 or vice versa
+// for the number at the end of the boot device. E.g., /dev/sda1 -> /dev/sda2
+// or /dev/sda4 -> /dev/sda3. See
+// http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
+bool GetInstallDev(const std::string& boot_dev, std::string* install_dev);
+
+// Decodes the data in |base64_encoded| and stores it in a temporary
+// file. Returns false if the given data is empty, not well-formed
+// base64 or if an error occurred. If true is returned, the decoded
+// data is stored in the file returned in |out_path|. The file should
+// be deleted when no longer needed.
+bool DecodeAndStoreBase64String(const std::string& base64_encoded,
+                                base::FilePath *out_path);
+
+// Converts |time| to an Omaha InstallDate which is defined as "the
+// number of PST8PDT calendar weeks since Jan 1st 2007 0:00 PST, times
+// seven" with PST8PDT defined as "Pacific Time" (e.g. UTC-07:00 if
+// daylight savings is observed and UTC-08:00 otherwise.)
+//
+// If the passed in |time| variable is before Monday January 1st 2007
+// 0:00 PST, False is returned and the value returned in
+// |out_num_days| is undefined. Otherwise the number of PST8PDT
+// calendar weeks since that date times seven is returned in
+// |out_num_days| and the function returns True.
+//
+// (NOTE: This function does not currently take daylight savings time
+// into account so the result may up to one hour off. This is because
+// the glibc date and timezone routines depend on the TZ environment
+// variable and changing environment variables is not thread-safe. An
+// alternative, thread-safe API to use would be GDateTime/GTimeZone
+// available in GLib 2.26 or later.)
+bool ConvertToOmahaInstallDate(base::Time time, int *out_num_days);
+
+// This function returns the duration on the wallclock since the last
+// time it was called for the same |state_variable_key| value.
+//
+// If the function returns |true|, the duration (always non-negative)
+// is returned in |out_duration|. If the function returns |false|
+// something went wrong or there was no previous measurement.
+bool WallclockDurationHelper(SystemState* system_state,
+                             const std::string& state_variable_key,
+                             base::TimeDelta* out_duration);
+
+// This function returns the duration on the monotonic clock since the
+// last time it was called for the same |storage| pointer.
+//
+// You should pass a pointer to a 64-bit integer in |storage| which
+// should be initialized to 0.
+//
+// If the function returns |true|, the duration (always non-negative)
+// is returned in |out_duration|. If the function returns |false|
+// something went wrong or there was no previous measurement.
+bool MonotonicDurationHelper(SystemState* system_state,
+                             int64_t* storage,
+                             base::TimeDelta* out_duration);
+
+// Look for the minor version value in the passed |store| and set
+// |minor_version| to that value. Return whether the value was found and valid.
+bool GetMinorVersion(const chromeos::KeyValueStore& store,
+                     uint32_t* minor_version);
+
+// This function reads the specified data in |extents| into |out_data|. The
+// extents are read from the file at |path|. |out_data_size| is the size of
+// |out_data|. Returns false if the number of bytes to read given in
+// |extents| does not equal |out_data_size|.
+bool ReadExtents(const std::string& path, const std::vector<Extent>& extents,
+                 chromeos::Blob* out_data, ssize_t out_data_size,
+                 size_t block_size);
+
+}  // namespace utils
+
+
+// Utility class to close a file descriptor
+class ScopedFdCloser {
+ public:
+  explicit ScopedFdCloser(int* fd) : fd_(fd), should_close_(true) {}
+  ~ScopedFdCloser() {
+    if (should_close_ && fd_ && (*fd_ >= 0)) {
+      if (!close(*fd_))
+        *fd_ = -1;
+    }
+  }
+  void set_should_close(bool should_close) { should_close_ = should_close; }
+ private:
+  int* fd_;
+  bool should_close_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedFdCloser);
+};
+
+// An EINTR-immune file descriptor closer.
+class ScopedEintrSafeFdCloser {
+ public:
+  explicit ScopedEintrSafeFdCloser(int* fd) : fd_(fd), should_close_(true) {}
+  ~ScopedEintrSafeFdCloser() {
+    if (should_close_ && fd_ && (*fd_ >= 0)) {
+      if (!IGNORE_EINTR(close(*fd_)))
+        *fd_ = -1;
+    }
+  }
+  void set_should_close(bool should_close) { should_close_ = should_close; }
+ private:
+  int* fd_;
+  bool should_close_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedEintrSafeFdCloser);
+};
+
+// Utility class to delete a file when it goes out of scope.
+class ScopedPathUnlinker {
+ public:
+  explicit ScopedPathUnlinker(const std::string& path)
+      : path_(path),
+        should_remove_(true) {}
+  ~ScopedPathUnlinker() {
+    if (should_remove_ && unlink(path_.c_str()) < 0) {
+      PLOG(ERROR) << "Unable to unlink path " << path_;
+    }
+  }
+  void set_should_remove(bool should_remove) { should_remove_ = should_remove; }
+
+ private:
+  const std::string path_;
+  bool should_remove_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedPathUnlinker);
+};
+
+// Utility class to delete an empty directory when it goes out of scope.
+class ScopedDirRemover {
+ public:
+  explicit ScopedDirRemover(const std::string& path)
+      : path_(path),
+        should_remove_(true) {}
+  ~ScopedDirRemover() {
+    if (should_remove_ && (rmdir(path_.c_str()) < 0)) {
+      PLOG(ERROR) << "Unable to remove dir " << path_;
+    }
+  }
+  void set_should_remove(bool should_remove) { should_remove_ = should_remove; }
+
+ protected:
+  const std::string path_;
+
+ private:
+  bool should_remove_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedDirRemover);
+};
+
+// Utility class to unmount a filesystem mounted on a temporary directory and
+// delete the temporary directory when it goes out of scope.
+class ScopedTempUnmounter : public ScopedDirRemover {
+ public:
+  explicit ScopedTempUnmounter(const std::string& path) :
+      ScopedDirRemover(path) {}
+  ~ScopedTempUnmounter() {
+    utils::UnmountFilesystem(path_);
+  }
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedTempUnmounter);
+};
+
+// A little object to call ActionComplete on the ActionProcessor when
+// it's destructed.
+class ScopedActionCompleter {
+ public:
+  explicit ScopedActionCompleter(ActionProcessor* processor,
+                                 AbstractAction* action)
+      : processor_(processor),
+        action_(action),
+        code_(ErrorCode::kError),
+        should_complete_(true) {}
+  ~ScopedActionCompleter() {
+    if (should_complete_)
+      processor_->ActionComplete(action_, code_);
+  }
+  void set_code(ErrorCode code) { code_ = code; }
+  void set_should_complete(bool should_complete) {
+    should_complete_ = should_complete;
+  }
+  ErrorCode get_code() const { return code_; }
+
+ private:
+  ActionProcessor* processor_;
+  AbstractAction* action_;
+  ErrorCode code_;
+  bool should_complete_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
+};
+
+}  // namespace chromeos_update_engine
+
+#define TEST_AND_RETURN_FALSE_ERRNO(_x)                                        \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      std::string _msg =                                                       \
+          chromeos_update_engine::utils::ErrnoNumberAsString(errno);           \
+      LOG(ERROR) << #_x " failed: " << _msg;                                   \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_FALSE(_x)                                              \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      LOG(ERROR) << #_x " failed.";                                            \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_ERRNO(_x)                                              \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      std::string _msg =                                                       \
+          chromeos_update_engine::utils::ErrnoNumberAsString(errno);           \
+      LOG(ERROR) << #_x " failed: " << _msg;                                   \
+      return;                                                                  \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN(_x)                                                    \
+  do {                                                                         \
+    bool _success = static_cast<bool>(_x);                                     \
+    if (!_success) {                                                           \
+      LOG(ERROR) << #_x " failed.";                                            \
+      return;                                                                  \
+    }                                                                          \
+  } while (0)
+
+#define TEST_AND_RETURN_FALSE_ERRCODE(_x)                                      \
+  do {                                                                         \
+    errcode_t _error = (_x);                                                   \
+    if (_error) {                                                              \
+      errno = _error;                                                          \
+      LOG(ERROR) << #_x " failed: " << _error;                                 \
+      return false;                                                            \
+    }                                                                          \
+  } while (0)
+
+#endif  // UPDATE_ENGINE_UTILS_H_
diff --git a/utils_unittest.cc b/utils_unittest.cc
new file mode 100644
index 0000000..4787e84
--- /dev/null
+++ b/utils_unittest.cc
@@ -0,0 +1,796 @@
+// Copyright (c) 2012 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/utils.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <chromeos/message_loops/fake_message_loop.h>
+#include <chromeos/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_clock.h"
+#include "update_engine/fake_prefs.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/prefs.h"
+#include "update_engine/test_utils.h"
+
+using chromeos::FakeMessageLoop;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class UtilsTest : public ::testing::Test { };
+
+TEST(UtilsTest, CanParseECVersion) {
+  // Should be able to parse and valid key value line.
+  EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
+  EXPECT_EQ("123456", utils::ParseECVersion(
+      "b=1231a fw_version=123456 a=fasd2"));
+  EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
+  EXPECT_EQ("00VFA616", utils::ParseECVersion(
+      "vendor=\"sam\" fw_version=\"00VFA616\""));
+
+  // For invalid entries, should return the empty string.
+  EXPECT_EQ("", utils::ParseECVersion("b=1231a fw_version a=fasd2"));
+}
+
+
+TEST(UtilsTest, KernelDeviceOfBootDevice) {
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice(""));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("foo"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda0"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda1"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda2"));
+  EXPECT_EQ("/dev/sda2", utils::KernelDeviceOfBootDevice("/dev/sda3"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda4"));
+  EXPECT_EQ("/dev/sda4", utils::KernelDeviceOfBootDevice("/dev/sda5"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda6"));
+  EXPECT_EQ("/dev/sda6", utils::KernelDeviceOfBootDevice("/dev/sda7"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda8"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/sda9"));
+
+  EXPECT_EQ("/dev/mmcblk0p2",
+            utils::KernelDeviceOfBootDevice("/dev/mmcblk0p3"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/mmcblk0p4"));
+
+  EXPECT_EQ("/dev/mtd2", utils::KernelDeviceOfBootDevice("/dev/ubi3"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/ubi4"));
+
+  EXPECT_EQ("/dev/mtd2",
+            utils::KernelDeviceOfBootDevice("/dev/ubiblock3_0"));
+  EXPECT_EQ("/dev/mtd4",
+            utils::KernelDeviceOfBootDevice("/dev/ubiblock5_0"));
+  EXPECT_EQ("/dev/mtd6",
+            utils::KernelDeviceOfBootDevice("/dev/ubiblock7_0"));
+  EXPECT_EQ("", utils::KernelDeviceOfBootDevice("/dev/ubiblock4_0"));
+}
+
+TEST(UtilsTest, ReadFileFailure) {
+  chromeos::Blob empty;
+  EXPECT_FALSE(utils::ReadFile("/this/doesn't/exist", &empty));
+}
+
+TEST(UtilsTest, ReadFileChunk) {
+  base::FilePath file;
+  EXPECT_TRUE(base::CreateTemporaryFile(&file));
+  ScopedPathUnlinker unlinker(file.value());
+  chromeos::Blob data;
+  const size_t kSize = 1024 * 1024;
+  for (size_t i = 0; i < kSize; i++) {
+    data.push_back(i % 255);
+  }
+  EXPECT_TRUE(utils::WriteFile(file.value().c_str(), data.data(), data.size()));
+  chromeos::Blob in_data;
+  EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), kSize, 10, &in_data));
+  EXPECT_TRUE(in_data.empty());
+  EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 0, -1, &in_data));
+  EXPECT_TRUE(data == in_data);
+  in_data.clear();
+  EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 10, 20, &in_data));
+  EXPECT_TRUE(chromeos::Blob(data.begin() + 10, data.begin() + 10 + 20) ==
+              in_data);
+}
+
+TEST(UtilsTest, ErrnoNumberAsStringTest) {
+  EXPECT_EQ("No such file or directory", utils::ErrnoNumberAsString(ENOENT));
+}
+
+TEST(UtilsTest, IsSymlinkTest) {
+  string temp_dir;
+  EXPECT_TRUE(utils::MakeTempDirectory("symlink-test.XXXXXX", &temp_dir));
+  string temp_file = temp_dir + "/temp-file";
+  EXPECT_TRUE(utils::WriteFile(temp_file.c_str(), "", 0));
+  string temp_symlink = temp_dir + "/temp-symlink";
+  EXPECT_EQ(0, symlink(temp_file.c_str(), temp_symlink.c_str()));
+  EXPECT_FALSE(utils::IsSymlink(temp_dir.c_str()));
+  EXPECT_FALSE(utils::IsSymlink(temp_file.c_str()));
+  EXPECT_TRUE(utils::IsSymlink(temp_symlink.c_str()));
+  EXPECT_FALSE(utils::IsSymlink("/non/existent/path"));
+  EXPECT_TRUE(test_utils::RecursiveUnlinkDir(temp_dir));
+}
+
+TEST(UtilsTest, GetDiskNameTest) {
+  EXPECT_EQ("/dev/sda", utils::GetDiskName("/dev/sda3"));
+  EXPECT_EQ("/dev/sdp", utils::GetDiskName("/dev/sdp1234"));
+  EXPECT_EQ("/dev/mmcblk0", utils::GetDiskName("/dev/mmcblk0p3"));
+  EXPECT_EQ("", utils::GetDiskName("/dev/mmcblk0p"));
+  EXPECT_EQ("", utils::GetDiskName("/dev/sda"));
+  EXPECT_EQ("/dev/ubiblock", utils::GetDiskName("/dev/ubiblock3_2"));
+  EXPECT_EQ("", utils::GetDiskName("/dev/foo/bar"));
+  EXPECT_EQ("", utils::GetDiskName("/"));
+  EXPECT_EQ("", utils::GetDiskName(""));
+}
+
+TEST(UtilsTest, SysfsBlockDeviceTest) {
+  EXPECT_EQ("/sys/block/sda", utils::SysfsBlockDevice("/dev/sda"));
+  EXPECT_EQ("", utils::SysfsBlockDevice("/foo/sda"));
+  EXPECT_EQ("", utils::SysfsBlockDevice("/dev/foo/bar"));
+  EXPECT_EQ("", utils::SysfsBlockDevice("/"));
+  EXPECT_EQ("", utils::SysfsBlockDevice("./"));
+  EXPECT_EQ("", utils::SysfsBlockDevice(""));
+}
+
+TEST(UtilsTest, IsRemovableDeviceTest) {
+  EXPECT_FALSE(utils::IsRemovableDevice(""));
+  EXPECT_FALSE(utils::IsRemovableDevice("/dev/non-existent-device"));
+}
+
+TEST(UtilsTest, GetPartitionNumberTest) {
+  EXPECT_EQ(3, utils::GetPartitionNumber("/dev/sda3"));
+  EXPECT_EQ(3, utils::GetPartitionNumber("/dev/sdz3"));
+  EXPECT_EQ(123, utils::GetPartitionNumber("/dev/sda123"));
+  EXPECT_EQ(2, utils::GetPartitionNumber("/dev/mmcblk0p2"));
+  EXPECT_EQ(0, utils::GetPartitionNumber("/dev/mmcblk0p"));
+  EXPECT_EQ(3, utils::GetPartitionNumber("/dev/ubiblock3_2"));
+  EXPECT_EQ(0, utils::GetPartitionNumber(""));
+  EXPECT_EQ(0, utils::GetPartitionNumber("/"));
+  EXPECT_EQ(0, utils::GetPartitionNumber("/dev/"));
+  EXPECT_EQ(0, utils::GetPartitionNumber("/dev/sda"));
+  EXPECT_EQ(10, utils::GetPartitionNumber("/dev/loop10"));
+  EXPECT_EQ(11, utils::GetPartitionNumber("/dev/loop28p11"));
+  EXPECT_EQ(10, utils::GetPartitionNumber("/dev/loop10_0"));
+  EXPECT_EQ(11, utils::GetPartitionNumber("/dev/loop28p11_0"));
+}
+
+TEST(UtilsTest, MakePartitionNameTest) {
+  EXPECT_EQ("/dev/sda4", utils::MakePartitionName("/dev/sda", 4));
+  EXPECT_EQ("/dev/sda123", utils::MakePartitionName("/dev/sda", 123));
+  EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionName("/dev/mmcblk", 2));
+  EXPECT_EQ("/dev/mmcblk0p2", utils::MakePartitionName("/dev/mmcblk0", 2));
+  EXPECT_EQ("/dev/loop8", utils::MakePartitionName("/dev/loop", 8));
+  EXPECT_EQ("/dev/loop12p2", utils::MakePartitionName("/dev/loop12", 2));
+  EXPECT_EQ("/dev/ubi5_0", utils::MakePartitionName("/dev/ubiblock", 5));
+  EXPECT_EQ("/dev/mtd4", utils::MakePartitionName("/dev/ubiblock", 4));
+  EXPECT_EQ("/dev/ubi3_0", utils::MakePartitionName("/dev/ubiblock", 3));
+  EXPECT_EQ("/dev/mtd2", utils::MakePartitionName("/dev/ubiblock", 2));
+  EXPECT_EQ("/dev/ubi1_0", utils::MakePartitionName("/dev/ubiblock", 1));
+}
+
+TEST(UtilsTest, MakePartitionNameForMountTest) {
+  EXPECT_EQ("/dev/sda4", utils::MakePartitionNameForMount("/dev/sda4"));
+  EXPECT_EQ("/dev/sda123", utils::MakePartitionNameForMount("/dev/sda123"));
+  EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionNameForMount("/dev/mmcblk2"));
+  EXPECT_EQ("/dev/mmcblk0p2",
+            utils::MakePartitionNameForMount("/dev/mmcblk0p2"));
+  EXPECT_EQ("/dev/loop0", utils::MakePartitionNameForMount("/dev/loop0"));
+  EXPECT_EQ("/dev/loop8", utils::MakePartitionNameForMount("/dev/loop8"));
+  EXPECT_EQ("/dev/loop12p2",
+            utils::MakePartitionNameForMount("/dev/loop12p2"));
+  EXPECT_EQ("/dev/ubiblock5_0",
+            utils::MakePartitionNameForMount("/dev/ubiblock5_0"));
+  EXPECT_EQ("/dev/mtd4",
+            utils::MakePartitionNameForMount("/dev/ubi4_0"));
+  EXPECT_EQ("/dev/ubiblock3_0",
+            utils::MakePartitionNameForMount("/dev/ubiblock3"));
+  EXPECT_EQ("/dev/mtd2", utils::MakePartitionNameForMount("/dev/ubi2"));
+  EXPECT_EQ("/dev/ubi1_0",
+            utils::MakePartitionNameForMount("/dev/ubiblock1"));
+}
+
+namespace {
+// Compares cpu shares and returns an integer that is less
+// than, equal to or greater than 0 if |shares_lhs| is,
+// respectively, lower than, same as or higher than |shares_rhs|.
+int CompareCpuShares(utils::CpuShares shares_lhs,
+                     utils::CpuShares shares_rhs) {
+  return static_cast<int>(shares_lhs) - static_cast<int>(shares_rhs);
+}
+}  // namespace
+
+// Tests the CPU shares enum is in the order we expect it.
+TEST(UtilsTest, CompareCpuSharesTest) {
+  EXPECT_LT(CompareCpuShares(utils::kCpuSharesLow,
+                             utils::kCpuSharesNormal), 0);
+  EXPECT_GT(CompareCpuShares(utils::kCpuSharesNormal,
+                             utils::kCpuSharesLow), 0);
+  EXPECT_EQ(CompareCpuShares(utils::kCpuSharesNormal,
+                             utils::kCpuSharesNormal), 0);
+  EXPECT_GT(CompareCpuShares(utils::kCpuSharesHigh,
+                             utils::kCpuSharesNormal), 0);
+}
+
+TEST(UtilsTest, FuzzIntTest) {
+  static const unsigned int kRanges[] = { 0, 1, 2, 20 };
+  for (unsigned int range : kRanges) {
+    const int kValue = 50;
+    for (int tries = 0; tries < 100; ++tries) {
+      int value = utils::FuzzInt(kValue, range);
+      EXPECT_GE(value, kValue - range / 2);
+      EXPECT_LE(value, kValue + range - range / 2);
+    }
+  }
+}
+
+TEST(UtilsTest, RunAsRootGetFilesystemSizeTest) {
+  string img;
+  EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+  ScopedPathUnlinker img_unlinker(img);
+  test_utils::CreateExtImageAtPath(img, nullptr);
+  // Extend the "partition" holding the file system from 10MiB to 20MiB.
+  EXPECT_EQ(0, test_utils::System(base::StringPrintf(
+      "dd if=/dev/zero of=%s seek=20971519 bs=1 count=1 status=none",
+      img.c_str())));
+  EXPECT_EQ(20 * 1024 * 1024, utils::FileSize(img));
+  int block_count = 0;
+  int block_size = 0;
+  EXPECT_TRUE(utils::GetFilesystemSize(img, &block_count, &block_size));
+  EXPECT_EQ(4096, block_size);
+  EXPECT_EQ(10 * 1024 * 1024 / 4096, block_count);
+}
+
+// Squashfs example filesystem, generated with:
+//   echo hola>hola
+//   mksquashfs hola hola.sqfs -noappend -nopad
+//   hexdump hola.sqfs -e '16/1 "%02x, " "\n"'
+const uint8_t kSquashfsFile[] = {
+  0x68, 0x73, 0x71, 0x73, 0x02, 0x00, 0x00, 0x00,  // magic, inodes
+  0x3e, 0x49, 0x61, 0x54, 0x00, 0x00, 0x02, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
+  0xc0, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00,  // flags, noids, major, minor
+  0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // root_inode
+  0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // bytes_used
+  0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+  0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x68, 0x6f, 0x6c, 0x61, 0x0a, 0x2c, 0x00, 0x78,
+  0xda, 0x63, 0x62, 0x58, 0xc2, 0xc8, 0xc0, 0xc0,
+  0xc8, 0xd0, 0x6b, 0x91, 0x18, 0x02, 0x64, 0xa0,
+  0x00, 0x56, 0x06, 0x90, 0xcc, 0x7f, 0xb0, 0xbc,
+  0x9d, 0x67, 0x62, 0x08, 0x13, 0x54, 0x1c, 0x44,
+  0x4b, 0x03, 0x31, 0x33, 0x10, 0x03, 0x00, 0xb5,
+  0x87, 0x04, 0x89, 0x16, 0x00, 0x78, 0xda, 0x63,
+  0x60, 0x80, 0x00, 0x46, 0x28, 0xcd, 0xc4, 0xc0,
+  0xcc, 0x90, 0x91, 0x9f, 0x93, 0x08, 0x00, 0x04,
+  0x70, 0x01, 0xab, 0x10, 0x80, 0x60, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
+  0x01, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x78,
+  0xda, 0x63, 0x60, 0x80, 0x00, 0x05, 0x28, 0x0d,
+  0x00, 0x01, 0x10, 0x00, 0x21, 0xc5, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x99,
+  0xcd, 0x02, 0x00, 0x88, 0x13, 0x00, 0x00, 0xdd,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+TEST(UtilsTest, GetSquashfs4Size) {
+  uint8_t buffer[sizeof(kSquashfsFile)];
+  memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+  int block_count = -1;
+  int block_size = -1;
+  // Not enough bytes passed.
+  EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+
+  // The whole file system is passed, which is enough for parsing.
+  EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+                                      &block_count, &block_size));
+  EXPECT_EQ(4096, block_size);
+  EXPECT_EQ(1, block_count);
+
+  // Modify the major version to 5.
+  uint16_t* s_major = reinterpret_cast<uint16_t*>(buffer + 0x1c);
+  *s_major = 5;
+  EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+  memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+  // Modify the bytes_used to have 6 blocks.
+  int64_t* bytes_used = reinterpret_cast<int64_t*>(buffer + 0x28);
+  *bytes_used = 4096 * 5 + 1;  // 6 "blocks".
+  EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+                                      &block_count, &block_size));
+  EXPECT_EQ(4096, block_size);
+  EXPECT_EQ(6, block_count);
+}
+
+TEST(UtilsTest, GetInstallDevTest) {
+  string boot_dev = "/dev/sda5";
+  string install_dev;
+  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+  EXPECT_EQ(install_dev, "/dev/sda3");
+
+  boot_dev = "/dev/sda3";
+  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+  EXPECT_EQ(install_dev, "/dev/sda5");
+
+  boot_dev = "/dev/sda12";
+  EXPECT_FALSE(utils::GetInstallDev(boot_dev, &install_dev));
+
+  boot_dev = "/dev/ubiblock3_0";
+  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+  EXPECT_EQ(install_dev, "/dev/ubi5_0");
+
+  boot_dev = "/dev/ubiblock5_0";
+  EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+  EXPECT_EQ(install_dev, "/dev/ubi3_0");
+
+  boot_dev = "/dev/ubiblock12_0";
+  EXPECT_FALSE(utils::GetInstallDev(boot_dev, &install_dev));
+}
+
+namespace {
+void GetFileFormatTester(const string& expected,
+                         const vector<uint8_t>& contents) {
+  test_utils::ScopedTempFile file;
+  ASSERT_TRUE(utils::WriteFile(file.GetPath().c_str(),
+                               reinterpret_cast<const char*>(contents.data()),
+                               contents.size()));
+  EXPECT_EQ(expected, utils::GetFileFormat(file.GetPath()));
+}
+}  // namespace
+
+TEST(UtilsTest, GetFileFormatTest) {
+  EXPECT_EQ("File not found.", utils::GetFileFormat("/path/to/nowhere"));
+  GetFileFormatTester("data", vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8});
+  GetFileFormatTester("ELF", vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46});
+
+  // Real tests from cros_installer on different boards.
+  // ELF 32-bit LSB executable, Intel 80386
+  GetFileFormatTester(
+      "ELF 32-bit little-endian x86",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0x90, 0x83, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00});
+
+  // ELF 32-bit LSB executable, MIPS
+  GetFileFormatTester(
+      "ELF 32-bit little-endian mips",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x03, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0xc0, 0x12, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+  // ELF 32-bit LSB executable, ARM
+  GetFileFormatTester(
+      "ELF 32-bit little-endian arm",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x02, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0x85, 0x8b, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+  // ELF 64-bit LSB executable, x86-64
+  GetFileFormatTester(
+      "ELF 64-bit little-endian x86-64",
+      vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
+                      0xb0, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00});
+}
+
+TEST(UtilsTest, ScheduleCrashReporterUploadTest) {
+  // Not much to test. At least this tests for memory leaks, crashes,
+  // log errors.
+  FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+  utils::ScheduleCrashReporterUpload();
+  // Test that we scheduled one callback from the crash reporter.
+  EXPECT_EQ(1, chromeos::MessageLoopRunMaxIterations(&loop, 100));
+  EXPECT_FALSE(loop.PendingTasks());
+}
+
+TEST(UtilsTest, FormatTimeDeltaTest) {
+  // utils::FormatTimeDelta() is not locale-aware (it's only used for logging
+  // which is not localized) so we only need to test the C locale
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromMilliseconds(100)),
+            "0.1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(0)),
+            "0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1)),
+            "1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(59)),
+            "59s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(60)),
+            "1m0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(61)),
+            "1m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(90)),
+            "1m30s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1205)),
+            "20m5s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3600)),
+            "1h0m0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3601)),
+            "1h0m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3661)),
+            "1h1m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(7261)),
+            "2h1m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86400)),
+            "1d0h0m0s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86401)),
+            "1d0h0m1s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000)),
+            "2d7h33m20s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000) +
+                                   base::TimeDelta::FromMilliseconds(1)),
+            "2d7h33m20.001s");
+  EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(-1)),
+            "-1s");
+}
+
+TEST(UtilsTest, TimeFromStructTimespecTest) {
+  struct timespec ts;
+
+  // Unix epoch (Thursday 00:00:00 UTC on Jan 1, 1970)
+  ts = (struct timespec) {.tv_sec = 0, .tv_nsec = 0};
+  EXPECT_EQ(base::Time::UnixEpoch(), utils::TimeFromStructTimespec(&ts));
+
+  // 42 ms after the Unix billennium (Sunday 01:46:40 UTC on September 9, 2001)
+  ts = (struct timespec) {.tv_sec = 1000 * 1000 * 1000,
+                          .tv_nsec = 42 * 1000 * 1000};
+  base::Time::Exploded exploded = (base::Time::Exploded) {
+    .year = 2001, .month = 9, .day_of_week = 0, .day_of_month = 9,
+    .hour = 1, .minute = 46, .second = 40, .millisecond = 42};
+  EXPECT_EQ(base::Time::FromUTCExploded(exploded),
+            utils::TimeFromStructTimespec(&ts));
+}
+
+TEST(UtilsTest, DecodeAndStoreBase64String) {
+  base::FilePath path;
+
+  // Ensure we return false on empty strings or invalid base64.
+  EXPECT_FALSE(utils::DecodeAndStoreBase64String("", &path));
+  EXPECT_FALSE(utils::DecodeAndStoreBase64String("not valid base64", &path));
+
+  // Pass known base64 and check that it matches. This string was generated
+  // the following way:
+  //
+  //   $ echo "Update Engine" | base64
+  //   VXBkYXRlIEVuZ2luZQo=
+  EXPECT_TRUE(utils::DecodeAndStoreBase64String("VXBkYXRlIEVuZ2luZQo=",
+                                                &path));
+  ScopedPathUnlinker unlinker(path.value());
+  string expected_contents = "Update Engine\n";
+  string contents;
+  EXPECT_TRUE(utils::ReadFile(path.value(), &contents));
+  EXPECT_EQ(contents, expected_contents);
+  EXPECT_EQ(utils::FileSize(path.value()), expected_contents.size());
+}
+
+TEST(UtilsTest, ConvertToOmahaInstallDate) {
+  // The Omaha Epoch starts at Jan 1, 2007 0:00 PST which is a
+  // Monday. In Unix time, this point in time is easily obtained via
+  // the date(1) command like this:
+  //
+  //  $ date +"%s" --date="Jan 1, 2007 0:00 PST"
+  const time_t omaha_epoch = 1167638400;
+  int value;
+
+  // Points in time *on and after* the Omaha epoch should not fail.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch), &value));
+  EXPECT_GE(value, 0);
+
+  // Anything before the Omaha epoch should fail. We test it for two points.
+  EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch - 1), &value));
+  EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch - 100*24*3600), &value));
+
+  // Check that we jump from 0 to 7 exactly on the one-week mark, e.g.
+  // on Jan 8, 2007 0:00 PST.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 7*24*3600 - 1), &value));
+  EXPECT_EQ(value, 0);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 7*24*3600), &value));
+  EXPECT_EQ(value, 7);
+
+  // Check a couple of more values.
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 10*24*3600), &value));
+  EXPECT_EQ(value, 7);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 20*24*3600), &value));
+  EXPECT_EQ(value, 14);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 26*24*3600), &value));
+  EXPECT_EQ(value, 21);
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(omaha_epoch + 29*24*3600), &value));
+  EXPECT_EQ(value, 28);
+
+  // The date Jun 4, 2007 0:00 PDT is a Monday and is hence a point
+  // where the Omaha InstallDate jumps 7 days. Its unix time is
+  // 1180940400. Notably, this is a point in time where Daylight
+  // Savings Time (DST) was is in effect (e.g. it's PDT, not PST).
+  //
+  // Note that as utils::ConvertToOmahaInstallDate() _deliberately_
+  // ignores DST (as it's hard to implement in a thread-safe way using
+  // glibc, see comments in utils.h) we have to fudge by the DST
+  // offset which is one hour. Conveniently, if the function were
+  // someday modified to be DST aware, this test would have to be
+  // modified as well.
+  const time_t dst_time = 1180940400;  // Jun 4, 2007 0:00 PDT.
+  const time_t fudge = 3600;
+  int value1, value2;
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(dst_time + fudge - 1), &value1));
+  EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+      base::Time::FromTimeT(dst_time + fudge), &value2));
+  EXPECT_EQ(value1, value2 - 7);
+}
+
+TEST(UtilsTest, WallclockDurationHelper) {
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  base::TimeDelta duration;
+  string state_variable_key = "test-prefs";
+  FakePrefs fake_prefs;
+
+  fake_system_state.set_clock(&fake_clock);
+  fake_system_state.set_prefs(&fake_prefs);
+
+  // Initialize wallclock to 1 sec.
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000));
+
+  // First time called so no previous measurement available.
+  EXPECT_FALSE(utils::WallclockDurationHelper(&fake_system_state,
+                                              state_variable_key,
+                                              &duration));
+
+  // Next time, we should get zero since the clock didn't advance.
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+
+  // We can also call it as many times as we want with it being
+  // considered a failure.
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+
+  // Advance the clock one second, then we should get 1 sec on the
+  // next call and 0 sec on the subsequent call.
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(2000000));
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 1);
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+
+  // Advance clock two seconds and we should get 2 sec and then 0 sec.
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000));
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 2);
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+
+  // There's a possibility that the wallclock can go backwards (NTP
+  // adjustments, for example) so check that we properly handle this
+  // case.
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(3000000));
+  EXPECT_FALSE(utils::WallclockDurationHelper(&fake_system_state,
+                                              state_variable_key,
+                                              &duration));
+  fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000));
+  EXPECT_TRUE(utils::WallclockDurationHelper(&fake_system_state,
+                                             state_variable_key,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 1);
+}
+
+TEST(UtilsTest, MonotonicDurationHelper) {
+  int64_t storage = 0;
+  FakeSystemState fake_system_state;
+  FakeClock fake_clock;
+  base::TimeDelta duration;
+
+  fake_system_state.set_clock(&fake_clock);
+
+  // Initialize monotonic clock to 1 sec.
+  fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000));
+
+  // First time called so no previous measurement available.
+  EXPECT_FALSE(utils::MonotonicDurationHelper(&fake_system_state,
+                                              &storage,
+                                              &duration));
+
+  // Next time, we should get zero since the clock didn't advance.
+  EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+                                             &storage,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+
+  // We can also call it as many times as we want with it being
+  // considered a failure.
+  EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+                                             &storage,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+  EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+                                             &storage,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+
+  // Advance the clock one second, then we should get 1 sec on the
+  // next call and 0 sec on the subsequent call.
+  fake_clock.SetMonotonicTime(base::Time::FromInternalValue(2000000));
+  EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+                                             &storage,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 1);
+  EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+                                             &storage,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+
+  // Advance clock two seconds and we should get 2 sec and then 0 sec.
+  fake_clock.SetMonotonicTime(base::Time::FromInternalValue(4000000));
+  EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+                                             &storage,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 2);
+  EXPECT_TRUE(utils::MonotonicDurationHelper(&fake_system_state,
+                                             &storage,
+                                             &duration));
+  EXPECT_EQ(duration.InSeconds(), 0);
+}
+
+TEST(UtilsTest, GetConnectionType) {
+  // Check that expected combinations map to the right value.
+  EXPECT_EQ(metrics::ConnectionType::kUnknown,
+            utils::GetConnectionType(kNetUnknown,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kWimax,
+            utils::GetConnectionType(kNetWimax,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kBluetooth,
+            utils::GetConnectionType(kNetBluetooth,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kCellular,
+            utils::GetConnectionType(kNetCellular,
+                                     NetworkTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kTetheredEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kConfirmed));
+  EXPECT_EQ(metrics::ConnectionType::kTetheredWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kConfirmed));
+
+  // Ensure that we don't report tethered ethernet unless it's confirmed.
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kNotDetected));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kSuspected));
+  EXPECT_EQ(metrics::ConnectionType::kEthernet,
+            utils::GetConnectionType(kNetEthernet,
+                                     NetworkTethering::kUnknown));
+
+  // Ditto for tethered wifi.
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kNotDetected));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kSuspected));
+  EXPECT_EQ(metrics::ConnectionType::kWifi,
+            utils::GetConnectionType(kNetWifi,
+                                     NetworkTethering::kUnknown));
+}
+
+TEST(UtilsTest, GetMinorVersion) {
+  // Test GetMinorVersion by verifying that it parses the conf file and returns
+  // the correct value.
+  uint32_t minor_version;
+
+  chromeos::KeyValueStore store;
+  EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+  EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=one-two-three\n"));
+  EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+  EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=123\n"));
+  EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+  EXPECT_EQ(minor_version, 123);
+}
+
+static bool BoolMacroTestHelper() {
+  int i = 1;
+  unsigned int ui = 1;
+  bool b = 1;
+  std::unique_ptr<char> cptr(new char);
+
+  TEST_AND_RETURN_FALSE(i);
+  TEST_AND_RETURN_FALSE(ui);
+  TEST_AND_RETURN_FALSE(b);
+  TEST_AND_RETURN_FALSE(cptr);
+
+  TEST_AND_RETURN_FALSE_ERRNO(i);
+  TEST_AND_RETURN_FALSE_ERRNO(ui);
+  TEST_AND_RETURN_FALSE_ERRNO(b);
+  TEST_AND_RETURN_FALSE_ERRNO(cptr);
+
+  return true;
+}
+
+static void VoidMacroTestHelper(bool* ret) {
+  int i = 1;
+  unsigned int ui = 1;
+  bool b = 1;
+  std::unique_ptr<char> cptr(new char);
+
+  *ret = false;
+
+  TEST_AND_RETURN(i);
+  TEST_AND_RETURN(ui);
+  TEST_AND_RETURN(b);
+  TEST_AND_RETURN(cptr);
+
+  TEST_AND_RETURN_ERRNO(i);
+  TEST_AND_RETURN_ERRNO(ui);
+  TEST_AND_RETURN_ERRNO(b);
+  TEST_AND_RETURN_ERRNO(cptr);
+
+  *ret = true;
+}
+
+TEST(UtilsTest, TestMacros) {
+  bool void_test = false;
+  VoidMacroTestHelper(&void_test);
+  EXPECT_TRUE(void_test);
+
+  EXPECT_TRUE(BoolMacroTestHelper());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/zip_unittest.cc b/zip_unittest.cc
new file mode 100644
index 0000000..4dcee59
--- /dev/null
+++ b/zip_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 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 <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/bzip.h"
+#include "update_engine/test_utils.h"
+#include "update_engine/utils.h"
+
+using chromeos_update_engine::test_utils::kRandomString;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+template <typename T>
+class ZipTest : public ::testing::Test {
+ public:
+  bool ZipDecompress(const chromeos::Blob& in,
+                     chromeos::Blob* out) const = 0;
+  bool ZipCompress(const chromeos::Blob& in,
+                   chromeos::Blob* out) const = 0;
+  bool ZipCompressString(const string& str,
+                         chromeos::Blob* out) const = 0;
+  bool ZipDecompressString(const string& str,
+                           chromeos::Blob* out) const = 0;
+};
+
+class BzipTest {};
+
+template <>
+class ZipTest<BzipTest> : public ::testing::Test {
+ public:
+  bool ZipDecompress(const chromeos::Blob& in,
+                     chromeos::Blob* out) const {
+    return BzipDecompress(in, out);
+  }
+  bool ZipCompress(const chromeos::Blob& in,
+                   chromeos::Blob* out) const {
+    return BzipCompress(in, out);
+  }
+  bool ZipCompressString(const string& str,
+                         chromeos::Blob* out) const {
+    return BzipCompressString(str, out);
+  }
+  bool ZipDecompressString(const string& str,
+                           chromeos::Blob* out) const {
+    return BzipDecompressString(str, out);
+  }
+};
+
+typedef ::testing::Types<BzipTest> ZipTestTypes;
+TYPED_TEST_CASE(ZipTest, ZipTestTypes);
+
+
+
+TYPED_TEST(ZipTest, SimpleTest) {
+  string in("this should compress well xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+  chromeos::Blob out;
+  EXPECT_TRUE(this->ZipCompressString(in, &out));
+  EXPECT_LT(out.size(), in.size());
+  EXPECT_GT(out.size(), 0);
+  chromeos::Blob decompressed;
+  EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
+  EXPECT_EQ(in.size(), decompressed.size());
+  EXPECT_TRUE(!memcmp(in.data(), decompressed.data(), in.size()));
+}
+
+TYPED_TEST(ZipTest, PoorCompressionTest) {
+  string in(reinterpret_cast<const char*>(kRandomString),
+            sizeof(kRandomString));
+  chromeos::Blob out;
+  EXPECT_TRUE(this->ZipCompressString(in, &out));
+  EXPECT_GT(out.size(), in.size());
+  string out_string(out.begin(), out.end());
+  chromeos::Blob decompressed;
+  EXPECT_TRUE(this->ZipDecompressString(out_string, &decompressed));
+  EXPECT_EQ(in.size(), decompressed.size());
+  EXPECT_TRUE(!memcmp(in.data(), decompressed.data(), in.size()));
+}
+
+TYPED_TEST(ZipTest, MalformedZipTest) {
+  string in(reinterpret_cast<const char*>(kRandomString),
+            sizeof(kRandomString));
+  chromeos::Blob out;
+  EXPECT_FALSE(this->ZipDecompressString(in, &out));
+}
+
+TYPED_TEST(ZipTest, EmptyInputsTest) {
+  string in;
+  chromeos::Blob out;
+  EXPECT_TRUE(this->ZipDecompressString(in, &out));
+  EXPECT_EQ(0, out.size());
+
+  EXPECT_TRUE(this->ZipCompressString(in, &out));
+  EXPECT_EQ(0, out.size());
+}
+
+}  // namespace chromeos_update_engine