Almost there...



git-svn-id: svn://chrome-svn/chromeos/trunk@24 06c00378-0e64-4dae-be16-12b19f9950a1
diff --git a/SConstruct b/SConstruct
new file mode 100644
index 0000000..d4c97ed
--- /dev/null
+++ b/SConstruct
@@ -0,0 +1,42 @@
+# 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.
+
+env = Environment()
+env['CCFLAGS'] = '-g -fno-exceptions -Wall -Werror -D_FILE_OFFSET_BITS=64 ' + \
+                 '-I/usr/include/libxml2'
+env['LIBS'] = Split('curl glog gtest ssl xml2 z')
+env['CPPPATH'] = ['..']
+env.ParseConfig('pkg-config --cflags --libs glib-2.0')
+
+if ARGUMENTS.get('debug', 0):
+  env['CCFLAGS'] += ' -fprofile-arcs -ftest-coverage'
+  env['LIBS'] += ['gcov']
+
+sources = Split("""action_processor.cc
+                   decompressing_file_writer.cc
+                   download_action.cc
+                   libcurl_http_fetcher.cc
+                   omaha_hash_calculator.cc
+                   update_check_action.cc""")
+main = ['main.cc']
+
+unittest_sources = Split("""action_unittest.cc
+                            action_pipe_unittest.cc
+                            action_processor_unittest.cc
+                            decompressing_file_writer_unittest.cc
+                            download_action_unittest.cc
+                            file_writer_unittest.cc
+                            http_fetcher_unittest.cc
+                            mock_http_fetcher.cc
+                            omaha_hash_calculator_unittest.cc
+                            test_utils.cc
+                            update_check_action_unittest.cc""")
+unittest_main = ['testrunner.cc']
+
+env.Program('update_engine', sources + main)
+unittest_cmd = env.Program('update_engine_unittests',
+            sources + unittest_sources + unittest_main)
+
+Clean(unittest_cmd, Glob('*.gcda') + Glob('*.gcno') + Glob('*.gcov') +
+                    Split('html app.info'))
diff --git a/action.h b/action.h
new file mode 100644
index 0000000..e973066
--- /dev/null
+++ b/action.h
@@ -0,0 +1,199 @@
+// 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 <tr1/memory>
+#include <iostream>
+
+#include "base/basictypes.h"
+#include <glog/logging.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 instatiation 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 instatiated 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.
+
+using std::tr1::shared_ptr;
+
+namespace chromeos_update_engine {
+
+// It is handy to have a non-templated base class of all Actions.
+class AbstractAction {
+ public:
+  AbstractAction() : processor_(NULL) {}
+
+  // 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 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 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:
+  virtual ~Action() {
+    LOG(INFO) << "Action died";
+  }
+
+  // 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 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 shared_ptr<ActionPipe<
+          typename ActionTraits<SubClass>::OutputObjectType> >&
+          out_pipe) {
+    out_pipe_ = out_pipe;
+  }
+ protected:
+  // 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);
+  }
+
+  // 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.
+  shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType> >
+      in_pipe_;
+  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..19926ed
--- /dev/null
+++ b/action_pipe.h
@@ -0,0 +1,94 @@
+// 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_PIPE_H__
+#define UPDATE_ENGINE_PIPE_H__
+
+#include <stdio.h>
+#include <iostream>
+#include <map>
+#include <string>
+#include <tr1/memory>
+#include <glog/logging.h>
+#include "base/basictypes.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.
+
+using std::map;
+using std::string;
+using std::tr1::shared_ptr;
+
+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() {
+    LOG(INFO) << "ActionPipe died";
+  }
+
+  // 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) {
+    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_PIPE_H__
diff --git a/action_pipe_unittest.cc b/action_pipe_unittest.cc
new file mode 100644
index 0000000..28cf248
--- /dev/null
+++ b/action_pipe_unittest.cc
@@ -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.
+
+#include <gtest/gtest.h>
+#include "update_engine/action.h"
+#include "update_engine/action_pipe.h"
+
+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.
+struct ActionPipeTestAction : public Action<ActionPipeTestAction> {
+  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..623e3d2
--- /dev/null
+++ b/action_processor.cc
@@ -0,0 +1,75 @@
+// 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 "update_engine/action.h"
+
+namespace chromeos_update_engine {
+
+ActionProcessor::ActionProcessor()
+    : current_action_(NULL), delegate_(NULL) {}
+
+ActionProcessor::~ActionProcessor() {
+  if (IsRunning()) {
+    StopProcessing();
+  }
+  for (std::deque<AbstractAction*>::iterator it = actions_.begin();
+       it != actions_.end(); ++it) {
+    (*it)->SetProcessor(NULL);
+  }
+}
+
+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(NULL);
+  LOG(INFO) << "ActionProcessor::StopProcessing: aborted "
+            << current_action_->Type();
+  current_action_ = NULL;
+  if (delegate_)
+    delegate_->ProcessingStopped(this);
+}
+
+void ActionProcessor::ActionComplete(const AbstractAction* actionptr,
+                                     bool success) {
+  CHECK_EQ(actionptr, current_action_);
+  if (delegate_)
+    delegate_->ActionCompleted(this, actionptr, success);
+  string old_type = current_action_->Type();
+  current_action_->SetProcessor(NULL);
+  current_action_ = NULL;
+  if (actions_.empty()) {
+    LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+                 " type " << old_type;
+    if (delegate_) {
+      delegate_->ProcessingDone(this);
+    }
+    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..d478477
--- /dev/null
+++ b/action_processor.h
@@ -0,0 +1,97 @@
+// 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_PROCESSOR_H__
+#define UPDATE_ENGINE_ACTION_PROCESSOR_H__
+
+#include <deque>
+
+#include "base/basictypes.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 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();
+
+  ~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.
+  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 NULL != current_action_; }
+
+  // Adds another Action to the end of the queue.
+  void EnqueueAction(AbstractAction* action);
+
+  // Sets the current delegate. Set to NULL to remove a 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(const AbstractAction* actionptr, bool success);
+
+ 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 currrently 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:
+  // Called when all processing in an ActionProcessor has completed. A pointer
+  // to the ActionProcessor is passed.
+  virtual void ProcessingDone(const ActionProcessor* processor) {}
+
+  // 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(const ActionProcessor* processor,
+                               const AbstractAction* action,
+                               bool success) {}
+};
+
+}  // 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..2a80bc3
--- /dev/null
+++ b/action_processor_unittest.cc
@@ -0,0 +1,168 @@
+// 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 <gtest/gtest.h>
+#include "update_engine/action.h"
+#include "update_engine/action_processor.h"
+
+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.
+struct ActionProcessorTestAction : public Action<ActionProcessorTestAction> {
+  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, true);
+  }
+  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) {}
+
+  virtual void ProcessingDone(const ActionProcessor* processor) {
+    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(const ActionProcessor* processor,
+                               const AbstractAction* action,
+                               bool success) {
+    EXPECT_EQ(processor_, processor);
+    EXPECT_FALSE(action_completed_called_);
+    action_completed_called_ = true;
+    action_completed_success_ = success;
+  }
+
+  const ActionProcessor* processor_;
+  bool processing_done_called_;
+  bool processing_stopped_called_;
+  bool action_completed_called_;
+  bool action_completed_success_;
+};
+}  // 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(NULL);
+  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(NULL);
+  EXPECT_TRUE(delegate.processing_stopped_called_);
+  EXPECT_FALSE(delegate.action_completed_called_);
+  EXPECT_FALSE(action_processor.IsRunning());
+  EXPECT_EQ(NULL, 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(NULL, 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(NULL, action1.processor());
+  EXPECT_FALSE(action1.IsRunning());
+  EXPECT_EQ(NULL, 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(NULL);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/action_unittest.cc b/action_unittest.cc
new file mode 100644
index 0000000..371f84d
--- /dev/null
+++ b/action_unittest.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 <gtest/gtest.h>
+#include "update_engine/action.h"
+#include "update_engine/action_processor.h"
+
+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.
+struct ActionTestAction : public Action<ActionTestAction> {
+  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, true);
+  }
+  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/decompressing_file_writer.cc b/decompressing_file_writer.cc
new file mode 100644
index 0000000..15bd4fd
--- /dev/null
+++ b/decompressing_file_writer.cc
@@ -0,0 +1,77 @@
+// 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/decompressing_file_writer.h"
+
+namespace chromeos_update_engine {
+
+//  typedef struct z_stream_s {
+//      Bytef    *next_in;  /* next input byte */
+//      uInt     avail_in;  /* number of bytes available at next_in */
+//      uLong    total_in;  /* total nb of input bytes read so far */
+//
+//      Bytef    *next_out; /* next output byte should be put there */
+//      uInt     avail_out; /* remaining free space at next_out */
+//      uLong    total_out; /* total nb of bytes output so far */
+//
+//      char     *msg;      /* last error message, NULL if no error */
+//      struct internal_state FAR *state; /* not visible by applications */
+//
+//      alloc_func zalloc;  /* used to allocate the internal state */
+//      free_func  zfree;   /* used to free the internal state */
+//      voidpf     opaque;  /* private data object passed to zalloc and zfree */
+//
+//      int     data_type;  /* best guess about the data type: binary or text */
+//      uLong   adler;      /* adler32 value of the uncompressed data */
+//      uLong   reserved;   /* reserved for future use */
+//  } z_stream;
+
+int GzipDecompressingFileWriter::Write(const void* bytes, size_t count) {
+  // Steps:
+  // put the data on next_in
+  // call inflate until it returns nothing, each time writing what we get
+  // check that next_in has no data left.
+
+  // It seems that zlib can keep internal buffers in the stream object,
+  // so not all data we get fed to us this time will necessarily
+  // be written out this time (in decompressed form).
+
+  CHECK_EQ(0, stream_.avail_in);
+  char buf[1024];
+
+  buffer_.reserve(count);
+  buffer_.clear();
+  CHECK_GE(buffer_.capacity(), count);
+  const char* char_bytes = reinterpret_cast<const char*>(bytes);
+  buffer_.insert(buffer_.end(), char_bytes, char_bytes + count);
+
+  stream_.next_in = reinterpret_cast<Bytef*>(&buffer_[0]);
+  stream_.avail_in = count;
+  int retcode = Z_OK;
+
+  while (Z_OK == retcode) {
+    stream_.next_out = reinterpret_cast<Bytef*>(buf);
+    stream_.avail_out = sizeof(buf);
+    int retcode = inflate(&stream_, Z_NO_FLUSH);
+    // check for Z_STREAM_END, Z_OK, or Z_BUF_ERROR (which is non-fatal)
+    if (Z_STREAM_END != retcode && Z_OK != retcode && Z_BUF_ERROR != retcode) {
+      LOG(ERROR) << "zlib inflate() error:" << retcode;
+      if (stream_.msg)
+        LOG(ERROR) << "message:" << stream_.msg;
+      return 0;
+    }
+    int count_received = sizeof(buf) - stream_.avail_out;
+    if (count_received > 0) {
+      next_->Write(buf, count_received);
+    } else {
+      // Inflate returned no data; we're done for now. Make sure no
+      // input data remain.
+      CHECK_EQ(0, stream_.avail_in);
+      break;
+    }
+  }
+  return count;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/decompressing_file_writer.h b/decompressing_file_writer.h
new file mode 100644
index 0000000..975ca3c
--- /dev/null
+++ b/decompressing_file_writer.h
@@ -0,0 +1,60 @@
+// 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_DECOMPRESSING_FILE_WRITER_H__
+#define UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
+
+#include <zlib.h>
+#include "base/basictypes.h"
+#include "update_engine/file_writer.h"
+
+// GzipDecompressingFileWriter is an implementation of FileWriter. It will
+// gzip decompress all data that is passed via Write() onto another FileWriter,
+// which is responsible for actually writing the data out. Calls to
+// Open and Close are passed through to the other FileWriter.
+
+namespace chromeos_update_engine {
+
+class GzipDecompressingFileWriter : public FileWriter {
+ public:
+  explicit GzipDecompressingFileWriter(FileWriter* next) : next_(next) {
+    memset(&stream_, 0, sizeof(stream_));
+    CHECK_EQ(inflateInit2(&stream_, 16 + MAX_WBITS), Z_OK);
+  }
+  virtual ~GzipDecompressingFileWriter() {
+    inflateEnd(&stream_);
+  }
+
+  virtual int Open(const char* path, int flags, mode_t mode) {
+    return next_->Open(path, flags, mode);
+  }
+
+  // Write() calls into zlib to decompress the bytes passed in. It will not
+  // necessarily write all the decompressed data associated with this set of
+  // passed-in compressed data during this call, however for a valid gzip
+  // stream, after the entire stream has been written to this object,
+  // the entire decompressed stream will have been written to the
+  // underlying FileWriter.
+  virtual int Write(const void* bytes, size_t count);
+
+  virtual int Close() {
+    return next_->Close();
+  }
+ private:
+  // The FileWriter that we write all uncompressed data to
+  FileWriter* next_;
+
+  // The zlib state
+  z_stream stream_;
+
+  // This is for an optimization in Write(). We can keep this buffer around
+  // in our class to avoid repeated calls to malloc().
+  std::vector<char> buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(GzipDecompressingFileWriter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DECOMPRESSING_FILE_WRITER_H__
diff --git a/decompressing_file_writer_unittest.cc b/decompressing_file_writer_unittest.cc
new file mode 100644
index 0000000..1d689e7
--- /dev/null
+++ b/decompressing_file_writer_unittest.cc
@@ -0,0 +1,107 @@
+// 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 <string.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/decompressing_file_writer.h"
+#include "update_engine/mock_file_writer.h"
+#include "update_engine/test_utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class GzipDecompressingFileWriterTest : public ::testing::Test { };
+
+TEST(GzipDecompressingFileWriterTest, SimpleTest) {
+  MockFileWriter mock_file_writer;
+  GzipDecompressingFileWriter decompressing_file_writer(&mock_file_writer);
+
+  // Here is the shell magic to include binary file in C source:
+  // hexdump -v -e '" " 12/1 "0x%02x, " "\n"' $FILENAME
+  // | sed -e '$s/0x  ,//g' -e 's/^/ /g' | awk
+  // 'BEGIN { print "unsigned char file[] = {" } END { print "};" } { print }'
+
+  // uncompressed, contains just 3 bytes: "hi\n"
+  unsigned char hi_txt_gz[] = {
+    0x1f, 0x8b, 0x08, 0x08, 0x62, 0xf5, 0x8a, 0x4a,
+    0x02, 0x03, 0x68, 0x69, 0x2e, 0x74, 0x78, 0x74,
+    0x00, 0xcb, 0xc8, 0xe4, 0x02, 0x00, 0x7a, 0x7a,
+    0x6f, 0xed, 0x03, 0x00, 0x00, 0x00,
+  };
+  char hi[] = "hi\n";
+  vector<char> hi_vector(hi, hi + strlen(hi));
+
+  const string path("unused");
+  ASSERT_EQ(0, decompressing_file_writer.Open(
+      path.c_str(), O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY, 0644));
+  ASSERT_EQ(sizeof(hi_txt_gz),
+            decompressing_file_writer.Write(hi_txt_gz, sizeof(hi_txt_gz)));
+  ASSERT_EQ(hi_vector.size(), mock_file_writer.bytes().size());
+  for (unsigned int i = 0; i < hi_vector.size(); i++) {
+    EXPECT_EQ(hi_vector[i], mock_file_writer.bytes()[i]) << "i = " << i;
+  }
+}
+
+TEST(GzipDecompressingFileWriterTest, IllegalStreamTest) {
+  MockFileWriter mock_file_writer;
+  GzipDecompressingFileWriter decompressing_file_writer(&mock_file_writer);
+
+  const string path("unused");
+  ASSERT_EQ(0, decompressing_file_writer.Open(
+      path.c_str(), O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY, 0644));
+  EXPECT_EQ(0, decompressing_file_writer.Write("\0\0\0\0\0\0\0\0", 8));
+  EXPECT_EQ(0, mock_file_writer.bytes().size());
+}
+
+TEST(GzipDecompressingFileWriterTest, LargeTest) {
+  const string kPath("/tmp/GzipDecompressingFileWriterTest");
+  const string kPathgz(kPath + ".gz");
+  // First, generate some data, say 10 megs:
+  DirectFileWriter uncompressed_file;
+  const char* k10bytes = "0123456789";
+  const unsigned int k10bytesSize = 10;
+  const unsigned int kUncompressedFileSize = strlen(k10bytes) * 1024 * 1024;
+  uncompressed_file.Open(kPath.c_str(),
+                         O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY, 0644);
+  for (unsigned int i = 0; i < kUncompressedFileSize / k10bytesSize; i++) {
+    ASSERT_EQ(k10bytesSize, uncompressed_file.Write("0123456789", 10));
+  }
+  uncompressed_file.Close();
+
+  // compress the file
+  system((string("cat ") + kPath + " | gzip > " + kPathgz).c_str());
+
+  // Now read the compressed file and put it into a DecompressingFileWriter
+  MockFileWriter mock_file_writer;
+  GzipDecompressingFileWriter decompressing_file_writer(&mock_file_writer);
+
+  const string path("unused");
+  ASSERT_EQ(0, decompressing_file_writer.Open(
+      path.c_str(), O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY, 0644));
+
+  // Open compressed file for reading:
+  int fd_in = open(kPathgz.c_str(), O_LARGEFILE | O_RDONLY, 0);
+  ASSERT_GE(fd_in, 0);
+  char buf[100];
+  int sz;
+  while ((sz = read(fd_in, buf, sizeof(buf))) > 0) {
+    decompressing_file_writer.Write(buf, sz);
+  }
+  close(fd_in);
+  decompressing_file_writer.Close();
+
+  ASSERT_EQ(kUncompressedFileSize, mock_file_writer.bytes().size());
+  for (unsigned int i = 0; i < kUncompressedFileSize; i++) {
+    ASSERT_EQ(mock_file_writer.bytes()[i], '0' + (i % 10)) << "i = " << i;
+  }
+  unlink(kPath.c_str());
+  unlink(kPathgz.c_str());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/download_action.cc b/download_action.cc
new file mode 100644
index 0000000..3a039d1
--- /dev/null
+++ b/download_action.cc
@@ -0,0 +1,93 @@
+// 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 <glib.h>
+
+#include "update_engine/action_pipe.h"
+#include "update_engine/download_action.h"
+
+namespace chromeos_update_engine {
+
+DownloadAction::DownloadAction(const std::string& url,
+                               const std::string& output_path,
+                               off_t size, const std::string& hash,
+                               const bool should_decompress,
+                               HttpFetcher* http_fetcher)
+    : size_(size),
+      url_(url),
+      output_path_(output_path),
+      hash_(hash),
+      should_decompress_(should_decompress),
+      writer_(NULL),
+      http_fetcher_(http_fetcher) {}
+
+DownloadAction::~DownloadAction() {}
+
+void DownloadAction::PerformAction() {
+  http_fetcher_->set_delegate(this);
+  CHECK(!writer_);
+  direct_file_writer_.reset(new DirectFileWriter);
+
+  if (should_decompress_) {
+    decompressing_file_writer_.reset(
+        new GzipDecompressingFileWriter(direct_file_writer_.get()));
+    writer_ = decompressing_file_writer_.get();
+  } else {
+    writer_ = direct_file_writer_.get();
+  }
+
+  int rc = writer_->Open(output_path_.c_str(),
+                         O_TRUNC | O_WRONLY | O_CREAT | O_LARGEFILE, 0644);
+  if (rc < 0) {
+    LOG(ERROR) << "Unable to open output file " << output_path_;
+    // report error to processor
+    processor_->ActionComplete(this, false);
+    return;
+  }
+  http_fetcher_->BeginTransfer(url_);
+}
+
+void DownloadAction::TerminateProcessing() {
+  CHECK(writer_);
+  CHECK_EQ(writer_->Close(), 0);
+  writer_ = NULL;
+  http_fetcher_->TerminateTransfer();
+}
+
+void DownloadAction::ReceivedBytes(HttpFetcher *fetcher,
+                                   const char* bytes,
+                                   int length) {
+  int bytes_written = 0;
+  do {
+    CHECK_GT(length, bytes_written);
+    int rc = writer_->Write(bytes + bytes_written, length - bytes_written);
+    // TODO(adlr): handle write error
+    CHECK_GE(rc, 0);
+    bytes_written += rc;
+  } while (length != bytes_written);
+  omaha_hash_calculator_.Update(bytes, length);
+}
+
+void DownloadAction::TransferComplete(HttpFetcher *fetcher, bool successful) {
+  if (writer_) {
+    CHECK_EQ(0, writer_->Close()) << errno;
+    writer_ = NULL;
+  }
+  if (successful) {
+    // Make sure hash is correct
+    omaha_hash_calculator_.Finalize();
+    if (omaha_hash_calculator_.hash() != hash_) {
+      LOG(INFO) << "Download of " << url_ << " failed. Expect hash "
+                << hash_ << " but got hash " << omaha_hash_calculator_.hash();
+      successful = false;
+    }
+  }
+
+  // Write the path to the output pipe if we're successful
+  if (successful && HasOutputPipe())
+    SetOutputObject(output_path_);
+  processor_->ActionComplete(this, successful);
+}
+
+};  // namespace {}
diff --git a/download_action.h b/download_action.h
new file mode 100644
index 0000000..9abbdcf
--- /dev/null
+++ b/download_action.h
@@ -0,0 +1,110 @@
+// 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_DOWNLOAD_ACTION_H__
+#define UPDATE_ENGINE_DOWNLOAD_ACTION_H__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <map>
+#include <string>
+
+#include <curl/curl.h>
+
+#include "base/scoped_ptr.h"
+#include "update_engine/action.h"
+#include "update_engine/decompressing_file_writer.h"
+#include "update_engine/file_writer.h"
+#include "update_engine/http_fetcher.h"
+#include "update_engine/omaha_hash_calculator.h"
+
+// The Download Action downloads a requested url to a specified path on disk.
+// It takes no input object, but if successful, passes the output path
+// to the output pipe.
+
+using std::map;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class DownloadAction;
+class NoneType;
+
+template<>
+class ActionTraits<DownloadAction> {
+ public:
+  // Does not take an object for input
+  typedef NoneType InputObjectType;
+  // On success, puts the output path on output
+  typedef std::string OutputObjectType;
+};
+
+class DownloadAction : public Action<DownloadAction>,
+                       public HttpFetcherDelegate {
+ public:
+  // Takes ownership of the passed in HttpFetcher. Useful for testing.
+  // A good calling pattern is:
+  // DownloadAction(..., new WhateverHttpFetcher);
+  DownloadAction(const std::string& url, const std::string& output_path,
+                 off_t size, const std::string& hash,
+                 const bool should_decompress,
+                 HttpFetcher* http_fetcher);
+  virtual ~DownloadAction();
+  typedef ActionTraits<DownloadAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<DownloadAction>::OutputObjectType OutputObjectType;
+  void PerformAction();
+  void TerminateProcessing();
+
+  // Debugging/logging
+  std::string Type() const { return "DownloadAction"; }
+
+  // Delegate methods (see http_fetcher.h)
+  virtual void ReceivedBytes(HttpFetcher *fetcher,
+                             const char* bytes, int length);
+  virtual void TransferComplete(HttpFetcher *fetcher, bool successful);
+
+ private:
+  // Expected size of the file (will be used for progress info)
+  const size_t size_;
+
+  // URL to download
+  const std::string url_;
+
+  // Path to save URL to
+  const std::string output_path_;
+
+  // Expected hash of the file. The hash must match for this action to
+  // succeed.
+  const std::string hash_;
+
+  // Whether the caller requested that we decompress the downloaded data.
+  const bool should_decompress_;
+
+  // The FileWriter that downloaded data should be written to. It will
+  // either point to *decompressing_file_writer_ or *direct_file_writer_.
+  FileWriter* writer_;
+
+  // If non-null, a FileWriter used for gzip decompressing downloaded data
+  scoped_ptr<GzipDecompressingFileWriter> decompressing_file_writer_;
+
+  // Used to write out the downloaded file
+  scoped_ptr<DirectFileWriter> direct_file_writer_;
+
+  // pointer to the HttpFetcher that does the http work
+  scoped_ptr<HttpFetcher> http_fetcher_;
+
+  // Used to find the hash of the bytes downloaded
+  OmahaHashCalculator omaha_hash_calculator_;
+  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..3dfc005
--- /dev/null
+++ b/download_action_unittest.cc
@@ -0,0 +1,264 @@
+// 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 <string>
+#include <vector>
+#include <glib.h>
+#include <gtest/gtest.h>
+#include "update_engine/action_pipe.h"
+#include "update_engine/download_action.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+class DownloadActionTest : public ::testing::Test { };
+
+namespace {
+class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  DownloadActionTestProcessorDelegate()
+      : loop_(NULL), processing_done_called_(false) {}
+  virtual ~DownloadActionTestProcessorDelegate() {
+    EXPECT_TRUE(processing_done_called_);
+  }
+  virtual void ProcessingDone(const ActionProcessor* processor) {
+    ASSERT_TRUE(loop_);
+    g_main_loop_quit(loop_);
+    vector<char> found_data = ReadFile(path_);
+    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;
+  }
+
+  virtual void ActionCompleted(const ActionProcessor* processor,
+                               const AbstractAction* action,
+                               bool success) {
+    // make sure actions always succeed
+    EXPECT_TRUE(success);
+  }
+
+  GMainLoop *loop_;
+  string path_;
+  vector<char> expected_data_;
+  bool processing_done_called_;
+};
+
+struct EntryPointArgs {
+  const vector<char> *data;
+  GMainLoop *loop;
+  ActionProcessor *processor;
+};
+
+gboolean StartProcessorInRunLoop(gpointer data) {
+  ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+  processor->StartProcessing();
+  return FALSE;
+}
+
+void TestWithData(const vector<char>& data, bool compress) {
+  vector<char> use_data;
+  if (compress) {
+    use_data = GzipCompressData(data);
+  } else {
+    use_data = data;
+  }
+
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  // TODO(adlr): see if we need a different file for build bots
+  const string path("/tmp/DownloadActionTest");
+  // takes ownership of passed in HttpFetcher
+  DownloadAction download_action("", path, 0,
+                                 OmahaHashCalculator::OmahaHashOfData(use_data),
+                                 compress, new MockHttpFetcher(&use_data[0],
+                                 use_data.size()));
+  DownloadActionTestProcessorDelegate delegate;
+  delegate.loop_ = loop;
+  delegate.expected_data_ = data;
+  delegate.path_ = path;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&download_action);
+
+  g_timeout_add(0, &StartProcessorInRunLoop, &processor);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+
+  // remove temp file; don't care if there are errors here
+  unlink(path.c_str());
+}
+}  // namespace {}
+
+TEST(DownloadActionTest, SimpleTest) {
+  vector<char> small;
+  const char* foo = "foo";
+  small.insert(small.end(), foo, foo + strlen(foo));
+  TestWithData(small, false);
+  TestWithData(small, true);
+}
+
+TEST(DownloadActionTest, LargeTest) {
+  vector<char> big(5 * kMockHttpFetcherChunkSize);
+  char c = '0';
+  for (unsigned int i = 0; i < big.size(); i++) {
+    big[i] = c;
+    if ('9' == c)
+      c = '0';
+    else
+      c++;
+  }
+  TestWithData(big, false);
+  TestWithData(big, true);
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  void ProcessingStopped(const ActionProcessor* processor) {
+    ASSERT_TRUE(loop_);
+    g_main_loop_quit(loop_);
+  }
+  GMainLoop *loop_;
+};
+
+gboolean TerminateEarlyTestStarter(gpointer data) {
+  ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+  processor->StartProcessing();
+  CHECK(processor->IsRunning());
+  processor->StopProcessing();
+  return FALSE;
+}
+
+}  // namespace {}
+
+TEST(DownloadActionTest, TerminateEarlyTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  vector<char> data(kMockHttpFetcherChunkSize + kMockHttpFetcherChunkSize / 2);
+  memset(&data[0], 0, data.size());
+
+  const string path("/tmp/DownloadActionTest");
+  {
+    // takes ownership of passed in HttpFetcher
+    DownloadAction download_action("", path, 0, "", false,
+        new MockHttpFetcher(&data[0], data.size()));
+    TerminateEarlyTestProcessorDelegate delegate;
+    delegate.loop_ = loop;
+    ActionProcessor processor;
+    processor.set_delegate(&delegate);
+    processor.EnqueueAction(&download_action);
+
+    g_timeout_add(0, &TerminateEarlyTestStarter, &processor);
+    g_main_loop_run(loop);
+    g_main_loop_unref(loop);
+  }
+
+  // 1 or 0 chunks should have come through
+  const off_t resulting_file_size(FileSize(path));
+  if (resulting_file_size != 0)
+    EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
+}
+
+class DownloadActionTestAction;
+
+template<>
+class ActionTraits<DownloadActionTestAction> {
+ public:
+  typedef string OutputObjectType;
+  typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+struct DownloadActionTestAction : public Action<DownloadActionTestAction> {
+  DownloadActionTestAction() : did_run_(false) {}
+  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() {
+    did_run_ = true;
+    ASSERT_TRUE(HasInputObject());
+    EXPECT_EQ(expected_input_object_, GetInputObject());
+    ASSERT_TRUE(processor());
+    processor()->ActionComplete(this, true);
+  }
+  string Type() const { return "DownloadActionTestAction"; }
+  string 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) {
+    ASSERT_TRUE(loop_);
+    g_main_loop_quit(loop_);
+  }
+  GMainLoop *loop_;
+};
+
+gboolean PassObjectOutTestStarter(gpointer data) {
+  ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+  processor->StartProcessing();
+  return FALSE;
+}
+}
+
+TEST(DownloadActionTest, PassObjectOutTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  const string path("/tmp/DownloadActionTest");
+
+  // takes ownership of passed in HttpFetcher
+  DownloadAction download_action("", path, 0,
+                                 OmahaHashCalculator::OmahaHashOfString("x"),
+                                 false, new MockHttpFetcher("x", 1));
+
+  DownloadActionTestAction test_action;
+  test_action.expected_input_object_ = path;
+  BondActions(&download_action, &test_action);
+
+  ActionProcessor processor;
+  PassObjectOutTestProcessorDelegate delegate;
+  delegate.loop_ = loop;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&download_action);
+  processor.EnqueueAction(&test_action);
+
+  g_timeout_add(0, &PassObjectOutTestStarter, &processor);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+
+  EXPECT_EQ(true, test_action.did_run_);
+}
+
+TEST(DownloadActionTest, BadOutFileTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  const string path("/fake/path/that/cant/be/created/because/of/missing/dirs");
+
+  // takes ownership of passed in HttpFetcher
+  DownloadAction download_action("", path, 0, "", false,
+                                 new MockHttpFetcher("x", 1));
+
+  ActionProcessor processor;
+  processor.EnqueueAction(&download_action);
+  processor.StartProcessing();
+  ASSERT_FALSE(processor.IsRunning());
+
+  g_main_loop_unref(loop);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/file_writer.h b/file_writer.h
new file mode 100644
index 0000000..223cd6e
--- /dev/null
+++ b/file_writer.h
@@ -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.
+
+#ifndef UPDATE_ENGINE_FILE_WRITER_H__
+#define UPDATE_ENGINE_FILE_WRITER_H__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include "glog/logging.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:
+  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 bytes written on success or
+  // -errno on error.
+  virtual int Write(const void* bytes, size_t count) = 0;
+
+  // Wrapper around close. Returns 0 on success or -errno on error.
+  virtual int Close() = 0;
+};
+
+// Direct file writer is probably the simplest FileWriter implementation.
+// It calls the system calls directly.
+
+class DirectFileWriter : public FileWriter {
+ public:
+  DirectFileWriter() : fd_(-1) {}
+  virtual ~DirectFileWriter() {}
+
+  virtual int Open(const char* path, int flags, mode_t mode) {
+    CHECK_EQ(-1, fd_);
+    fd_ = open(path, flags, mode);
+    if (fd_ < 0)
+      return -errno;
+    return 0;
+  }
+
+  virtual int Write(const void* bytes, size_t count) {
+    CHECK_GE(fd_, 0);
+    ssize_t rc = write(fd_, bytes, count);
+    if (rc < 0)
+      return -errno;
+    return rc;
+  }
+
+  virtual int 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;
+  }
+
+ private:
+  int fd_;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_FILE_WRITER_H__
\ No newline at end of file
diff --git a/file_writer_unittest.cc b/file_writer_unittest.cc
new file mode 100644
index 0000000..ed8bcaa
--- /dev/null
+++ b/file_writer_unittest.cc
@@ -0,0 +1,52 @@
+// 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 <string.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include <gtest/gtest.h>
+#include "update_engine/file_writer.h"
+#include "update_engine/test_utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FileWriterTest : public ::testing::Test { };
+
+TEST(FileWriterTest, SimpleTest) {
+  DirectFileWriter file_writer;
+  const string path("/tmp/FileWriterTest");
+  ASSERT_EQ(0, file_writer.Open(path.c_str(),
+                                O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY,
+                                0644));
+  ASSERT_EQ(4, file_writer.Write("test", 4));
+  vector<char> actual_data = ReadFile(path);
+
+  EXPECT_FALSE(memcmp("test", &actual_data[0], actual_data.size()));
+  EXPECT_EQ(0, file_writer.Close());
+  unlink(path.c_str());
+}
+
+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) {
+  DirectFileWriter file_writer;
+  const string path("/tmp/FileWriterTest");
+  EXPECT_EQ(0, file_writer.Open(path.c_str(),
+                                O_CREAT | O_LARGEFILE | O_TRUNC | O_RDONLY,
+                                0644));
+  EXPECT_EQ(-EBADF, file_writer.Write("x", 1));
+  EXPECT_EQ(0, file_writer.Close());
+}
+
+
+}  // namespace chromeos_update_engine
diff --git a/gen_coverage_html.sh b/gen_coverage_html.sh
new file mode 100755
index 0000000..7ccc5d8
--- /dev/null
+++ b/gen_coverage_html.sh
@@ -0,0 +1,21 @@
+#!/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.
+
+set -ex
+
+scons debug=1 -c
+scons debug=1
+lcov -d . --zerocounters
+./update_engine_unittests
+lcov --directory . --capture --output-file app.info
+
+# some versions of genhtml support the --no-function-coverage argument,
+# which we want. The problem w/ function coverage is that every template
+# instantiation of a method counts as a different method, so if we
+# instantiate a method twice, once for testing and once for prod, the method
+# is tested, but it shows only 50% function coverage b/c it thinks we didn't
+# test the prod version.
+genhtml --no-function-coverage -o html ./app.info || genhtml -o html ./app.info
diff --git a/http_fetcher.h b/http_fetcher.h
new file mode 100644
index 0000000..6e26906
--- /dev/null
+++ b/http_fetcher.h
@@ -0,0 +1,90 @@
+// 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 <string>
+#include <glib.h>
+#include "base/basictypes.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 glib main loop to request callbacks when timers or file descriptors
+// change.
+
+namespace chromeos_update_engine {
+
+class HttpFetcherDelegate;
+
+class HttpFetcher {
+ public:
+  HttpFetcher() : delegate_(NULL) {}
+  virtual ~HttpFetcher() {}
+  void set_delegate(HttpFetcherDelegate* delegate) {
+    delegate_ = delegate;
+  }
+  HttpFetcherDelegate* delegate() const {
+    return delegate_;
+  }
+
+  // Optional: Post data to the server. The HttpFetcher should make a copy
+  // of this data and upload it via HTTP POST during the transfer.
+  void SetPostData(const void* data, size_t size) {
+    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);
+  }
+
+  // Begins the transfer to the specified URL.
+  virtual void BeginTransfer(const std::string& url) = 0;
+
+  // Aborts the transfer. TransferComplete() will not be called on the
+  // delegate.
+  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;
+
+ 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_;
+  std::vector<char> post_data_;
+
+  // The delegate; may be NULL.
+  HttpFetcherDelegate* delegate_;
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HttpFetcher);
+};
+
+// Interface for delegates
+class HttpFetcherDelegate {
+ public:
+  // Called every time bytes are received, even if they are automatically
+  // delivered to an output file.
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const char* bytes,
+                             int length) = 0;
+
+  // Called when the transfer has completed successfully or been somehow
+  // aborted.
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
+};
+
+}  // 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..92edf69
--- /dev/null
+++ b/http_fetcher_unittest.cc
@@ -0,0 +1,277 @@
+// 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 <unistd.h>
+#include <base/scoped_ptr.h>
+#include <glib.h>
+#include <glog/logging.h>
+#include <gtest/gtest.h>
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/mock_http_fetcher.h"
+
+namespace chromeos_update_engine {
+
+namespace {
+// WARNING, if you update this, you must also update test_http_server.py
+const char* const kServerPort = "8080";
+string LocalServerUrlForPath(const string& path) {
+  return string("http://127.0.0.1:") + kServerPort + path;
+}
+}
+
+template <typename T>
+class HttpFetcherTest : public ::testing::Test {
+ public:
+  HttpFetcher* NewLargeFetcher() = 0;
+  HttpFetcher* NewSmallFetcher() = 0;
+  string BigUrl() const = 0;
+  string SmallUrl() const = 0;
+};
+
+class NullHttpServer {
+ public:
+  NullHttpServer() : started_(true) {}
+  ~NullHttpServer() {}
+  bool started_;
+};
+
+
+template <>
+class HttpFetcherTest<MockHttpFetcher> : public ::testing::Test {
+ public:
+  HttpFetcher* NewLargeFetcher() {
+    vector<char> big_data(1000000);
+    return new MockHttpFetcher(big_data.data(), big_data.size());
+  }
+  HttpFetcher* NewSmallFetcher() {
+    return new MockHttpFetcher("x", 1);
+  }
+  string BigUrl() const {
+    return "unused://unused";
+  }
+  string SmallUrl() const {
+    return "unused://unused";
+  }
+  typedef NullHttpServer HttpServer;
+};
+
+class PythonHttpServer {
+ public:
+  PythonHttpServer() {
+    char *argv[2] = {strdup("./test_http_server.py"), NULL};
+    GError *err;
+    started_ = false;
+    if (!g_spawn_async(NULL,
+                       argv,
+                       NULL,
+                       G_SPAWN_DO_NOT_REAP_CHILD,
+                       NULL,
+                       NULL,
+                       &pid_,
+                       &err)) {
+      return;
+    }
+    int rc = 1;
+    while (0 != rc) {
+      
+      rc = system((string("wget --output-document=/dev/null ") +
+                   LocalServerUrlForPath("/test")).c_str());
+      usleep(10 * 1000);  // 10 ms
+    }
+    started_ = true;
+    free(argv[0]);
+    return;
+  }
+  ~PythonHttpServer() {
+    if (!started_)
+      return;
+    // request that the server exit itself
+    system((string("wget --output-document=/dev/null ") +
+            LocalServerUrlForPath("/quitquitquit")).c_str());
+    waitpid(pid_, NULL, 0);
+  }
+  GPid pid_;
+  bool started_;
+};
+
+template <>
+class HttpFetcherTest<LibcurlHttpFetcher> : public ::testing::Test {
+ public:
+  HttpFetcher* NewLargeFetcher() {
+    LibcurlHttpFetcher *ret = new LibcurlHttpFetcher;
+    ret->set_idle_ms(1);  // speeds up test execution
+    return ret;
+  }
+  HttpFetcher* NewSmallFetcher() {
+    return NewLargeFetcher();
+  }
+  string BigUrl() const {
+    return LocalServerUrlForPath("/big");
+  }
+  string SmallUrl() const {
+    return LocalServerUrlForPath("/foo");
+  }
+  typedef PythonHttpServer HttpServer;
+};
+
+typedef ::testing::Types<LibcurlHttpFetcher, MockHttpFetcher>
+    HttpFetcherTestTypes;
+TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
+
+namespace {
+class HttpFetcherTestDelegate : public HttpFetcherDelegate {
+public:
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const char* bytes, int length) {
+    char str[length + 1];
+    memset(str, 0, length + 1);
+    memcpy(str, bytes, length);
+  }
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) {
+    g_main_loop_quit(loop_);
+  }
+  GMainLoop* loop_;
+};
+}  // namespace {}
+
+TYPED_TEST(HttpFetcherTest, SimpleTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  {
+    HttpFetcherTestDelegate delegate;
+    delegate.loop_ = loop;
+    scoped_ptr<HttpFetcher> fetcher(this->NewSmallFetcher());
+    fetcher->set_delegate(&delegate);
+
+    typename TestFixture::HttpServer server;
+    ASSERT_TRUE(server.started_);
+
+    fetcher->BeginTransfer(this->SmallUrl());
+    g_main_loop_run(loop);
+  }
+  g_main_loop_unref(loop);
+}
+
+namespace {
+class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const char* bytes, int length) {
+    char str[length + 1];
+    LOG(INFO) << "got " << length << " bytes";
+    memset(str, 0, length + 1);
+    memcpy(str, bytes, length);
+    CHECK(!paused_);
+    paused_ = true;
+    fetcher->Pause();
+    LOG(INFO) << "calling pause";
+  }
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) {
+    g_main_loop_quit(loop_);
+  }
+  void Unpause() {
+    CHECK(paused_);
+    paused_ = false;
+    fetcher_->Unpause();
+    LOG(INFO) << "calling unpause";
+  }
+  bool paused_;
+  HttpFetcher* fetcher_;
+  GMainLoop* loop_;
+};
+
+gboolean UnpausingTimeoutCallback(gpointer data) {
+  PausingHttpFetcherTestDelegate *delegate =
+      reinterpret_cast<PausingHttpFetcherTestDelegate*>(data);
+  if (delegate->paused_)
+    delegate->Unpause();
+  return TRUE;
+}
+}  // namespace {}
+
+TYPED_TEST(HttpFetcherTest, PauseTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  {
+    PausingHttpFetcherTestDelegate delegate;
+    scoped_ptr<HttpFetcher> fetcher(this->NewLargeFetcher());
+    delegate.paused_ = false;
+    delegate.loop_ = loop;
+    delegate.fetcher_ = fetcher.get();
+    fetcher->set_delegate(&delegate);
+
+    typename TestFixture::HttpServer server;
+    ASSERT_TRUE(server.started_);
+    GSource* timeout_source_;
+    timeout_source_ = g_timeout_source_new(0);  // ms
+    g_source_set_callback(timeout_source_, UnpausingTimeoutCallback, &delegate,
+                          NULL);
+    g_source_attach(timeout_source_, NULL);
+    fetcher->BeginTransfer(this->BigUrl());
+
+    g_main_loop_run(loop);
+    g_source_destroy(timeout_source_);
+  }
+  g_main_loop_unref(loop);
+}
+
+namespace {
+class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const char* bytes, int length) {}
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful) {
+    CHECK(false);  // We should never get here
+    g_main_loop_quit(loop_);
+  }
+  void TerminateTransfer() {
+    CHECK(once_);
+    once_ = false;
+    fetcher_->TerminateTransfer();
+  }
+  void EndLoop() {
+    g_main_loop_quit(loop_);
+  }
+  bool once_;
+  HttpFetcher* fetcher_;
+  GMainLoop* loop_;
+};
+
+gboolean AbortingTimeoutCallback(gpointer data) {
+  AbortingHttpFetcherTestDelegate *delegate =
+      reinterpret_cast<AbortingHttpFetcherTestDelegate*>(data);
+  if (delegate->once_) {
+    delegate->TerminateTransfer();
+    return TRUE;
+  } else {
+    delegate->EndLoop();
+    return FALSE;
+  }
+}
+}  // namespace {}
+
+TYPED_TEST(HttpFetcherTest, AbortTest) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  {
+    AbortingHttpFetcherTestDelegate delegate;
+    scoped_ptr<HttpFetcher> fetcher(this->NewLargeFetcher());
+    delegate.once_ = true;
+    delegate.loop_ = loop;
+    delegate.fetcher_ = fetcher.get();
+    fetcher->set_delegate(&delegate);
+
+    typename TestFixture::HttpServer server;
+    ASSERT_TRUE(server.started_);
+    GSource* timeout_source_;
+    timeout_source_ = g_timeout_source_new(0);  // ms
+    g_source_set_callback(timeout_source_, AbortingTimeoutCallback, &delegate,
+                          NULL);
+    g_source_attach(timeout_source_, NULL);
+    fetcher->BeginTransfer(this->BigUrl());
+
+    g_main_loop_run(loop);
+    g_source_destroy(timeout_source_);
+  }
+  g_main_loop_unref(loop);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
new file mode 100644
index 0000000..ab48694
--- /dev/null
+++ b/libcurl_http_fetcher.cc
@@ -0,0 +1,226 @@
+// 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 "glog/logging.h"
+#include "update_engine/libcurl_http_fetcher.h"
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+LibcurlHttpFetcher::~LibcurlHttpFetcher() {
+  CleanUp();
+}
+
+// Begins the transfer, which must not have already been started.
+void LibcurlHttpFetcher::BeginTransfer(const std::string& url) {
+  CHECK(!transfer_in_progress_);
+  url_ = url;
+  curl_multi_handle_ = curl_multi_init();
+  CHECK(curl_multi_handle_);
+
+  curl_handle_ = curl_easy_init();
+  CHECK(curl_handle_);
+
+  if (post_data_set_) {
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POST, 1));
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
+                                        &post_data_[0]));
+    CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
+                                        post_data_.size()));
+  }
+
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this));
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
+                                      StaticLibcurlWrite));
+  CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()));
+  CHECK_EQ(CURLM_OK, curl_multi_add_handle(curl_multi_handle_, curl_handle_));
+  transfer_in_progress_ = true;
+  CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::TerminateTransfer() {
+  CleanUp();
+}
+
+// TODO(adlr): detect network failures
+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 (0 == running_handles) {
+    // we're done!
+    CleanUp();
+    if (delegate_)
+      delegate_->TransferComplete(this, true);  // success
+  } else {
+    // set up callback
+    SetupMainloopSources();
+  }
+}
+
+size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
+  if (delegate_)
+    delegate_->ReceivedBytes(this, reinterpret_cast<char*>(ptr), size * nmemb);
+  return size * nmemb;
+}
+
+void LibcurlHttpFetcher::Pause() {
+  CHECK(curl_handle_);
+  CHECK(transfer_in_progress_);
+  CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_ALL));
+}
+
+void LibcurlHttpFetcher::Unpause() {
+  CHECK(curl_handle_);
+  CHECK(transfer_in_progress_);
+  CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_CONT));
+}
+
+// This method sets up callbacks with the glib main loop.
+void LibcurlHttpFetcher::SetupMainloopSources() {
+  fd_set fd_read;
+  fd_set fd_write;
+  fd_set fd_exec;
+
+  FD_ZERO(&fd_read);
+  FD_ZERO(&fd_write);
+  FD_ZERO(&fd_exec);
+
+  int fd_max = 0;
+
+  // Ask libcurl for the set of file descriptors we should track on its
+  // behalf.
+  CHECK_EQ(CURLM_OK, curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
+                                      &fd_exec, &fd_max));
+
+  // We should iterate through all file descriptors up to libcurl's fd_max or
+  // the highest one we're tracking, whichever is larger
+  if (!io_channels_.empty())
+    fd_max = max(fd_max, io_channels_.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 GIOChannel objects
+  // in io_channels_ as there are fds that we're tracking.
+  for (int i = 0; i <= fd_max; i++) {
+    if (!(FD_ISSET(i, &fd_read) || FD_ISSET(i, &fd_write) ||
+        FD_ISSET(i, &fd_exec))) {
+      // if we have an outstanding io_channel, remove it
+      if (io_channels_.find(i) != io_channels_.end()) {
+        g_source_remove(io_channels_[i].second);
+        g_io_channel_unref(io_channels_[i].first);
+        io_channels_.erase(io_channels_.find(i));
+      }
+      continue;
+    }
+    // If we are already tracking this fd, continue.
+    if (io_channels_.find(i) != io_channels_.end())
+      continue;
+
+    // We must track a new fd
+    GIOChannel *io_channel = g_io_channel_unix_new(i);
+    guint tag = g_io_add_watch(
+        io_channel,
+        static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI |
+                                  G_IO_ERR | G_IO_HUP),
+        &StaticFDCallback,
+        this);
+    io_channels_[i] = make_pair(io_channel, tag);
+  }
+
+  // Wet up a timeout callback for libcurl
+  long ms = 0;
+  CHECK_EQ(CURLM_OK, curl_multi_timeout(curl_multi_handle_, &ms));
+  if (ms < 0) {
+    // 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.
+    ms = idle_ms_;
+  }
+  if (timeout_source_) {
+    g_source_destroy(timeout_source_);
+    timeout_source_ = NULL;
+  }
+  timeout_source_ = g_timeout_source_new(ms);
+  CHECK(timeout_source_);
+  g_source_set_callback(timeout_source_, StaticTimeoutCallback, this,
+                        NULL);
+  g_source_attach(timeout_source_, NULL);
+}
+
+bool LibcurlHttpFetcher::FDCallback(GIOChannel *source,
+                                    GIOCondition condition) {
+  // Figure out which source it was; hopefully there aren't too many b/c
+  // this is a linear scan of our channels
+  bool found_in_set = false;
+  for (IOChannels::iterator it = io_channels_.begin();
+       it != io_channels_.end(); ++it) {
+    if (it->second.first == source) {
+      // We will return false from this method, meaning that we shouldn't keep
+      // this g_io_channel around. So we remove it now from our collection of
+      // g_io_channels so that the other code in this class doens't mess with
+      // this (doomed) GIOChannel.
+      // TODO(adlr): optimize by seeing if we should reuse this GIOChannel
+      g_source_remove(it->second.second);
+      g_io_channel_unref(it->second.first);
+      io_channels_.erase(it);
+      found_in_set = true;
+      break;
+    }
+  }
+  CHECK(found_in_set);
+  CurlPerformOnce();
+  return false;
+}
+
+bool LibcurlHttpFetcher::TimeoutCallback() {
+  // Since we will return false from this function, which tells glib to
+  // destroy the timeout callback, we must NULL it out here. This way, when
+  // setting up callback sources again, we won't try to delete this (doomed)
+  // timeout callback then.
+  // TODO(adlr): optimize by checking if we can keep this timeout callback.
+  timeout_source_ = NULL;
+  CurlPerformOnce();
+  return false;
+}
+
+void LibcurlHttpFetcher::CleanUp() {
+  if (timeout_source_) {
+    g_source_destroy(timeout_source_);
+    timeout_source_ = NULL;
+  }
+
+  for (IOChannels::iterator it = io_channels_.begin();
+       it != io_channels_.end(); ++it) {
+    g_source_remove(it->second.second);
+    g_io_channel_unref(it->second.first);
+  }
+  io_channels_.clear();
+
+  if (curl_handle_) {
+    if (curl_multi_handle_) {
+      CHECK_EQ(CURLM_OK,
+               curl_multi_remove_handle(curl_multi_handle_, curl_handle_));
+    }
+    curl_easy_cleanup(curl_handle_);
+    curl_handle_ = NULL;
+  }
+  if (curl_multi_handle_) {
+    CHECK_EQ(CURLM_OK, curl_multi_cleanup(curl_multi_handle_));
+    curl_multi_handle_ = NULL;
+  }
+  transfer_in_progress_ = false;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/libcurl_http_fetcher.h b/libcurl_http_fetcher.h
new file mode 100644
index 0000000..0ca31b6
--- /dev/null
+++ b/libcurl_http_fetcher.h
@@ -0,0 +1,118 @@
+// 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 <curl/curl.h>
+#include <glib.h>
+#include "base/basictypes.h"
+#include "glog/logging.h"
+#include "update_engine/http_fetcher.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()
+      : curl_multi_handle_(NULL), curl_handle_(NULL),
+        timeout_source_(NULL), transfer_in_progress_(false),
+        idle_ms_(1000) {}
+
+  // Cleans up all internal state. Does not notify delegate
+  ~LibcurlHttpFetcher();
+
+  // Begins the transfer if it hasn't already begun.
+  virtual void BeginTransfer(const std::string& url);
+
+  // If the transfer is in progress, aborts the transfer early.
+  // The transfer cannot be resumed.
+  virtual void TerminateTransfer();
+
+  // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL).
+  virtual void Pause();
+
+  // Resume the transfer by calling curl_easy_pause(CURLPAUSE_CONT).
+  virtual void Unpause();
+
+  // 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_ms(long ms) {
+    idle_ms_ = ms;
+  }
+ private:
+
+  // These two methods are for glib main loop callbacks. They are called
+  // when either a file descriptor is ready for work or when a timer
+  // has fired. The static versions are shims for libcurl which has a C API.
+  bool FDCallback(GIOChannel *source, GIOCondition condition);
+  static gboolean StaticFDCallback(GIOChannel *source,
+                                   GIOCondition condition,
+                                   gpointer data) {
+    return reinterpret_cast<LibcurlHttpFetcher*>(data)->FDCallback(source,
+                                                                   condition);
+  }
+  bool TimeoutCallback();
+  static gboolean StaticTimeoutCallback(gpointer data) {
+    return reinterpret_cast<LibcurlHttpFetcher*>(data)->TimeoutCallback();
+  }
+
+  // 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.
+  void CurlPerformOnce();
+
+  // Sets up glib main 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 SetupMainloopSources();
+
+  // 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, io_channels_, timeout_source_.
+  void CleanUp();
+
+  // Handles for the libcurl library
+  CURLM *curl_multi_handle_;
+  CURL *curl_handle_;
+
+  // a list of all file descriptors that we're waiting on from the
+  // glib main loop
+  typedef std::map<int, std::pair<GIOChannel*, guint> > IOChannels;
+  IOChannels io_channels_;
+
+  // if non-NULL, a timer we're waiting on. glib main loop will call us back
+  // when it fires.
+  GSource* timeout_source_;
+
+  bool transfer_in_progress_;
+
+  long idle_ms_;
+  DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H__
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..21add3b
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,35 @@
+// 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 <stdlib.h>
+#include <stdio.h>
+#include <iostream>
+#include <glib.h>
+#include <glog/logging.h>
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+// This code runs inside the main loop
+gboolean EntryPoint(gpointer data) {
+  GMainLoop *loop = reinterpret_cast<GMainLoop*>(data);
+  LOG(INFO) << "Chrome OS Update Engine beginning";
+  g_main_loop_quit(loop);
+  return FALSE;
+}
+
+int main(int argc, char** argv) {
+  xmlDocPtr doc = xmlNewDoc((const xmlChar*)"1.0");
+  CHECK(doc);
+  CHECK_EQ(argc, 2);
+  printf("enc: [%s]\n", xmlEncodeEntitiesReentrant(doc, (const xmlChar*)argv[1]));
+  return 0;
+  
+  
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  g_timeout_add(0, &EntryPoint, loop);
+  g_main_loop_run(loop);
+  return 0;
+}
diff --git a/mock_file_writer.h b/mock_file_writer.h
new file mode 100644
index 0000000..e6d1612
--- /dev/null
+++ b/mock_file_writer.h
@@ -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.
+
+#ifndef UPDATE_ENGINE_MOCK_FILE_WRITER_H__
+#define UPDATE_ENGINE_MOCK_FILE_WRITER_H__
+
+#include "base/basictypes.h"
+#include "update_engine/file_writer.h"
+
+// MockFileWriter 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 MockFileWriter : public FileWriter {
+ public:
+  MockFileWriter() : 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 int 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 std::vector<char>& bytes() {
+    return bytes_;
+  }
+ private:
+  // The internal store of all bytes that have been written
+  std::vector<char> bytes_;
+
+  // These are just to ensure FileWriter methods are called properly.
+  bool was_opened_;
+  bool was_closed_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockFileWriter);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_FILE_WRITER_H__
diff --git a/mock_http_fetcher.cc b/mock_http_fetcher.cc
new file mode 100644
index 0000000..0c4209a
--- /dev/null
+++ b/mock_http_fetcher.cc
@@ -0,0 +1,99 @@
+// 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 "glog/logging.h"
+#include "update_engine/mock_http_fetcher.h"
+
+// This is a mac implementation of HttpFetcher which is useful for testing.
+
+namespace chromeos_update_engine {
+
+MockHttpFetcher::~MockHttpFetcher() {
+  CHECK(!timeout_source_) << "Call TerminateTransfer() before dtor.";
+}
+
+void MockHttpFetcher::BeginTransfer(const std::string& url) {
+  if (sent_size_ < data_.size())
+    SendData(true);
+}
+
+// Returns false on one condition: If timeout_source_ was already set
+// and it needs to be deleted by the caller. If timeout_source_ is NULL
+// when this function is called, this function will always return true.
+bool MockHttpFetcher::SendData(bool skip_delivery) {
+  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);
+    sent_size_ += chunk_size;
+    CHECK_LE(sent_size_, data_.size());
+    if (sent_size_ == data_.size()) {
+      // We've sent all the data. notify of success
+      delegate_->TransferComplete(this, true);
+    }
+  }
+
+  if (paused_) {
+    // If we're paused, we should return true if timeout_source_ is set,
+    // since we need the caller to delete it.
+    return timeout_source_;
+  }
+
+  if (timeout_source_) {
+    // 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_source_ = g_timeout_source_new(10);
+    CHECK(timeout_source_);
+    g_source_set_callback(timeout_source_, StaticTimeoutCallback, this,
+                          NULL);
+    timout_tag_ = g_source_attach(timeout_source_, NULL);
+  }
+  return true;
+}
+
+bool MockHttpFetcher::TimeoutCallback() {
+  CHECK(!paused_);
+  bool ret = SendData(false);
+  if (false == ret) {
+    timeout_source_ = NULL;
+  }
+  return ret;
+}
+
+// If the transfer is in progress, aborts the transfer early.
+// The transfer cannot be resumed.
+void MockHttpFetcher::TerminateTransfer() {
+  sent_size_ = data_.size();
+  // kill any timeout
+  if (timeout_source_) {
+    g_source_remove(timout_tag_);
+    g_source_destroy(timeout_source_);
+    timeout_source_ = NULL;
+  }
+}
+
+void MockHttpFetcher::Pause() {
+  CHECK(!paused_);
+  paused_ = true;
+  if (timeout_source_) {
+    g_source_remove(timout_tag_);
+    g_source_destroy(timeout_source_);
+    timeout_source_ = NULL;
+  }
+
+}
+
+void MockHttpFetcher::Unpause() {
+  CHECK(paused_) << "You must pause before unpause.";
+  paused_ = false;
+  if (sent_size_ < data_.size()) {
+    SendData(false);
+  }
+}
+
+}  // namespace chromeos_update_engine
diff --git a/mock_http_fetcher.h b/mock_http_fetcher.h
new file mode 100644
index 0000000..3dc0645
--- /dev/null
+++ b/mock_http_fetcher.h
@@ -0,0 +1,94 @@
+// 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 <vector>
+#include <glib.h>
+#include <glog/logging.h>
+#include "update_engine/http_fetcher.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 char* data, size_t size)
+      : sent_size_(0), timeout_source_(NULL), timout_tag_(0), paused_(false) {
+    data_.insert(data_.end(), data, data + size);
+    LOG(INFO) << "timeout_source_ = " << (int)timeout_source_;
+  }
+
+  // Cleans up all internal state. Does not notify delegate
+  ~MockHttpFetcher();
+
+  // Begins the transfer if it hasn't already begun.
+  virtual void BeginTransfer(const std::string& url);
+
+  // If the transfer is in progress, aborts the transfer early.
+  // The transfer cannot be resumed.
+  virtual void TerminateTransfer();
+
+  // Suspend the mock transfer.
+  virtual void Pause();
+
+  // Resume the mock transfer.
+  virtual void Unpause();
+
+  // Fail the transfer. This simulates a network failure.
+  void FailTransfer();
+
+  const std::vector<char>& post_data() const {
+    return post_data_;
+  }
+  
+ private:
+  // Sends data to the delegate and sets up a glib 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 still be set if needed
+  bool SendData(bool skip_delivery);
+
+  // Callback for when our glib main loop callback is called
+  bool TimeoutCallback();
+  static gboolean StaticTimeoutCallback(gpointer data) {
+    return reinterpret_cast<MockHttpFetcher*>(data)->TimeoutCallback();
+  }
+
+  // A full copy of the data we'll return to the delegate
+  std::vector<char> data_;
+
+  // The number of bytes we've sent so far
+  size_t sent_size_;
+
+  // The glib main loop timeout source. After each chunk of data sent, we
+  // time out for 0s just to make sure that run loop services other clients.
+  GSource* timeout_source_;
+
+  // ID of the timeout source, valid only if timeout_source_ != NULL
+  guint timout_tag_;
+
+  // True iff the fetcher is paused.
+  bool paused_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_MOCK_HTTP_FETCHER_H__
diff --git a/omaha_hash_calculator.cc b/omaha_hash_calculator.cc
new file mode 100644
index 0000000..41dcdee
--- /dev/null
+++ b/omaha_hash_calculator.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 <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/evp.h>
+#include "glog/logging.h"
+#include "update_engine/omaha_hash_calculator.h"
+
+namespace chromeos_update_engine {
+
+OmahaHashCalculator::OmahaHashCalculator() {
+  CHECK_EQ(1, SHA1_Init(&ctx_));
+}
+
+// Update is called with all of the data that should be hashed in order.
+// Mostly just passes the data through to OpenSSL's SHA1_Update()
+void OmahaHashCalculator::Update(const char* data, size_t length) {
+  CHECK(hash_.empty()) << "Can't Update after hash is finalized";
+  COMPILE_ASSERT(sizeof(size_t) <= sizeof(unsigned long),
+                 length_param_may_be_truncated_in_SHA1_Update);
+  CHECK_EQ(1, SHA1_Update(&ctx_, data, length));
+}
+
+// Call Finalize() when all data has been passed in. This mostly just
+// calls OpenSSL's SHA1_Final() and then base64 encodes the hash.
+void OmahaHashCalculator::Finalize() {
+  CHECK(hash_.empty()) << "Don't call Finalize() twice";
+  unsigned char md[SHA_DIGEST_LENGTH];
+  CHECK_EQ(1, SHA1_Final(md, &ctx_));
+
+  // Convert md to base64 encoding and store it in hash_
+  BIO *b64 = BIO_new(BIO_f_base64());
+  CHECK(b64);
+  BIO *bmem = BIO_new(BIO_s_mem());
+  CHECK(bmem);
+  b64 = BIO_push(b64, bmem);
+  CHECK_EQ(sizeof(md), BIO_write(b64, md, sizeof(md)));
+  CHECK_EQ(1, BIO_flush(b64));
+
+  BUF_MEM *bptr = NULL;
+  BIO_get_mem_ptr(b64, &bptr);
+  hash_.assign(bptr->data, bptr->length - 1);
+
+  BIO_free_all(b64);
+}
+
+std::string OmahaHashCalculator::OmahaHashOfBytes(
+    const void* data, size_t length) {
+  OmahaHashCalculator calc;
+  calc.Update(reinterpret_cast<const char*>(data), length);
+  calc.Finalize();
+  return calc.hash();
+}
+
+std::string OmahaHashCalculator::OmahaHashOfString(
+    const std::string& str) {
+  return OmahaHashOfBytes(str.data(), str.size());
+}
+
+std::string OmahaHashCalculator::OmahaHashOfData(
+    const std::vector<char>& data) {
+  return OmahaHashOfBytes(&data[0], data.size());
+}
+
+}  // namespace chromeos_update_engine
diff --git a/omaha_hash_calculator.h b/omaha_hash_calculator.h
new file mode 100644
index 0000000..28602a3
--- /dev/null
+++ b/omaha_hash_calculator.h
@@ -0,0 +1,56 @@
+// 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 "base/basictypes.h"
+#include <string>
+#include <openssl/sha.h>
+
+// Omaha uses base64 encoded SHA-1 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|
+   void Update(const char* data, size_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.
+   void Finalize();
+
+  // Gets the hash. Finalize() must have been called.
+  const std::string& hash() const {
+    CHECK(!hash_.empty()) << "Call Finalize() first";
+    return 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 std::vector<char>& data);
+
+ private:
+  // If non-empty, the final base64 encoded hash. Will only be set to
+  // non-empty when Finalize is called.
+  std::string hash_;
+
+  // The hash state used by OpenSSL
+  SHA_CTX ctx_;
+  DISALLOW_COPY_AND_ASSIGN(OmahaHashCalculator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_OMAHA_HASH_CALCULATOR_H__
\ No newline at end of file
diff --git a/omaha_hash_calculator_unittest.cc b/omaha_hash_calculator_unittest.cc
new file mode 100644
index 0000000..0ee3b80
--- /dev/null
+++ b/omaha_hash_calculator_unittest.cc
@@ -0,0 +1,66 @@
+// 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 <math.h>
+#include <unistd.h>
+#include <glib.h>
+#include <gtest/gtest.h>
+#include "update_engine/libcurl_http_fetcher.h"
+#include "update_engine/omaha_hash_calculator.h"
+
+namespace chromeos_update_engine {
+
+class OmahaHashCalculatorTest : public ::testing::Test { };
+
+TEST(OmahaHashCalculatorTest, SimpleTest) {
+  OmahaHashCalculator calc;
+  calc.Update("hi", 2);
+  calc.Finalize();
+  // Generated by running this on a linux shell:
+  // $ echo -n hi | openssl sha1 -binary | openssl base64
+  EXPECT_EQ("witfkXg0JglCjW9RssWvTAveakI=", calc.hash());
+}
+
+TEST(OmahaHashCalculatorTest, MultiUpdateTest) {
+  OmahaHashCalculator calc;
+  calc.Update("h", 1);
+  calc.Update("i", 1);
+  calc.Finalize();
+  // Generated by running this on a linux shell:
+  // $ echo -n hi | openssl sha1 -binary | openssl base64
+  EXPECT_EQ("witfkXg0JglCjW9RssWvTAveakI=", calc.hash());
+}
+
+TEST(OmahaHashCalculatorTest, BigTest) {
+  OmahaHashCalculator calc;
+
+  for (int i = 0; i < 1000000; i++) {
+    char buf[8];
+    ASSERT_EQ(0 == i ? 1 : static_cast<int>(floorf(logf(i) / logf(10))) + 1,
+              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 sha1 -binary | openssl base64
+  EXPECT_EQ("qdNsMeRqzoEUu5/ABi+MGRli87s=", calc.hash());
+}
+
+TEST(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/test_http_server.py b/test_http_server.py
new file mode 100755
index 0000000..404e0e0
--- /dev/null
+++ b/test_http_server.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+# 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 is a simple HTTP server that's used by the
+# libcurl_http_fetcher_unittest, though it could be used by others. In
+# general, you can fork off this server, repeatedly request /test or
+# some URL until that URL succeeds; then you know the server is
+# running. The url /big returns 100,000 bytes of predictable data. The
+# url /quitquitquit causes the server to exit.
+
+import SimpleHTTPServer, BaseHTTPServer, httplib
+
+class TestHttpRequestHandler (SimpleHTTPServer.SimpleHTTPRequestHandler):
+  def do_GET(self):
+    # Exit the server
+    if self.path == '/quitquitquit':
+      self.server.stop = True
+
+    # Write 100,000 bytes out
+    if self.path == '/big':
+      self.send_response(200, 'OK')
+      self.send_header('Content-type', 'text/html')
+      self.end_headers()
+      for i in range(0, 10000):
+        try:
+          self.wfile.write('abcdefghij');  # 10 characters
+        except IOError:
+          return
+      return
+
+    # Everything else
+    self.send_response(200, 'OK')
+    self.send_header('Content-type', 'text/html')
+    self.end_headers()
+    self.wfile.write('unhandled path')
+
+class TestHttpServer (BaseHTTPServer.HTTPServer):
+  def serve_forever(self):
+    self.stop = False
+    while not self.stop:
+      self.handle_request()
+
+def main():
+  # TODO(adlr): Choose a port that works with build bots and report it to
+  # caller.
+  # WARNING, if you update this, you must also update http_fetcher_unittest.cc
+  port = 8080
+  server = TestHttpServer(('', 8080), TestHttpRequestHandler)
+  server.serve_forever()
+
+if __name__ == '__main__':
+  main()
diff --git a/test_utils.cc b/test_utils.cc
new file mode 100644
index 0000000..2791d44
--- /dev/null
+++ b/test_utils.cc
@@ -0,0 +1,73 @@
+// 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 <sys/stat.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string>
+#include <vector>
+#include "update_engine/test_utils.h"
+#include "glog/logging.h"
+
+#include "update_engine/file_writer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+vector<char> ReadFile(const string& path) {
+  vector<char> ret;
+  FILE* fp = fopen(path.c_str(), "r");
+  if (!fp)
+    return ret;
+  const size_t kChunkSize(1024);
+  size_t read_size;
+  do {
+    char buf[kChunkSize];
+    read_size = fread(buf, 1, kChunkSize, fp);
+    ret.insert(ret.end(), buf, buf + read_size);
+  } while (read_size == kChunkSize);
+  fclose(fp);
+  return ret;
+}
+
+bool WriteFile(const std::string& path, const std::vector<char>& data) {
+  DirectFileWriter writer;
+  if (0 != writer.Open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)) {
+    return false;
+  }
+  if (static_cast<int>(data.size()) != writer.Write(&data[0], data.size())) {
+    writer.Close();
+    return false;
+  }
+  writer.Close();
+  return true;
+}
+
+off_t FileSize(const string& path) {
+  struct stat stbuf;
+  int rc = stat(path.c_str(), &stbuf);
+  CHECK_EQ(0, rc);
+  if (rc < 0)
+    return rc;
+  return stbuf.st_size;
+}
+
+std::vector<char> GzipCompressData(const std::vector<char>& data) {
+  const char fname[] = "/tmp/GzipCompressDataTemp";
+  if (!WriteFile(fname, data)) {
+    system((string("rm ") + fname).c_str());
+    return vector<char>();
+  }
+  system((string("cat ") + fname + "|gzip>" + fname + ".gz").c_str());
+  system((string("rm ") + fname).c_str());
+  vector<char> ret = ReadFile(string(fname) + ".gz");
+  system((string("rm ") + fname + ".gz").c_str());
+  return ret;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/test_utils.h b/test_utils.h
new file mode 100644
index 0000000..0c12e13
--- /dev/null
+++ b/test_utils.h
@@ -0,0 +1,33 @@
+// 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_TEST_UTILS_H__
+#define UPDATE_ENGINE_TEST_UTILS_H__
+
+#include <vector>
+#include <string>
+
+// These are some handy functions for unittests.
+
+namespace chromeos_update_engine {
+
+// Returns the entire contents of the file at path. If the file doesn't
+// exist or error occurrs, an empty vector is returned.
+std::vector<char> ReadFile(const std::string& path);
+
+// 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 std::string& path, const std::vector<char>& data);
+
+// Returns the size of the file at path. If the file doesn't exist or some
+// error occurrs, -1 is returned.
+off_t FileSize(const std::string& path);
+
+// Gzip compresses the data passed using the gzip command line program.
+// Returns compressed data back.
+std::vector<char> GzipCompressData(const std::vector<char>& data);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_TEST_UTILS_H__
\ No newline at end of file
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..8931902
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,12 @@
+// 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.
+
+// based on pam_google_testrunner.cc
+
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/update_check_action.cc b/update_check_action.cc
new file mode 100644
index 0000000..a4942a6
--- /dev/null
+++ b/update_check_action.cc
@@ -0,0 +1,281 @@
+// 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/update_check_action.h"
+#include <sstream>
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include "update_engine/action_pipe.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char* const UpdateCheckParams::kAppId(
+    "87efface-864d-49a5-9bb3-4b050a7c227a");
+const char* const UpdateCheckParams::kOsPlatform("Chrome OS");
+const char* const UpdateCheckParams::kOsVersion("Indy");
+
+namespace {
+
+const string kGupdateVersion("ChromeOSUpdateEngine-0.1.0.0");
+
+// This is handy for passing strings into libxml2
+#define ConstXMLStr(x) (reinterpret_cast<const xmlChar*>(x))
+
+// These are for scoped_ptr_malloc, which is like scoped_ptr, but allows
+// a custom free() function to be specified.
+class ScopedPtrXmlDocFree {
+ public:
+  inline void operator()(void* x) const {
+    xmlFreeDoc(reinterpret_cast<xmlDoc*>(x));
+  }
+};
+class ScopedPtrXmlFree {
+ public:
+  inline void operator()(void* x) const {
+    xmlFree(x);
+  }
+};
+class ScopedPtrXmlXPathObjectFree {
+ public:
+  inline void operator()(void* x) const {
+    xmlXPathFreeObject(reinterpret_cast<xmlXPathObject*>(x));
+  }
+};
+class ScopedPtrXmlXPathContextFree {
+ public:
+  inline void operator()(void* x) const {
+    xmlXPathFreeContext(reinterpret_cast<xmlXPathContext*>(x));
+  }
+};
+
+// Returns a properly formatted omaha request for an update check
+string FormatRequest(const UpdateCheckParams& params) {
+  return string("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      "<o:gupdate xmlns:o=\"http://www.google.com/update2/request\" "
+      "version=\"" + XmlEncode(kGupdateVersion) + "\" protocol=\"2.0\" "
+      "machineid=\"") + XmlEncode(params.machine_id) + "\" ismachine=\"1\" "
+      "userid=\"" + XmlEncode(params.user_id) + "\">\n"
+      "    <o:os version=\"" + XmlEncode(params.os_version) + "\" platform=\"" +
+      XmlEncode(params.os_platform) + "\" sp=\"" +
+      XmlEncode(params.os_sp) + "\"></o:os>\n"
+      "    <o:app appid=\"" + XmlEncode(params.app_id) + "\" version=\"" +
+      XmlEncode(params.app_version) + "\" "
+      "lang=\"" + XmlEncode(params.app_lang) + "\" track=\"" +
+      XmlEncode(params.app_track) + "\">\n"
+      "        <o:ping active=\"0\"></o:ping>\n"
+      "        <o:updatecheck></o:updatecheck>\n"
+      "    </o:app>\n"
+      "</o:gupdate>\n";
+}
+}  // namespace {}
+
+// Encodes XML entities in a given string with libxml2. input must be
+// UTF-8 formatted. Output will be UTF-8 formatted.
+string XmlEncode(const string& input) {
+//  // TODO(adlr): if allocating a new xmlDoc each time is taking up too much
+//  // cpu, considering creating one and caching it.
+//  scoped_ptr_malloc<xmlDoc, ScopedPtrXmlDocFree> xml_doc(
+//      xmlNewDoc(ConstXMLStr("1.0")));
+//  if (!xml_doc.get()) {
+//    LOG(ERROR) << "Unable to create xmlDoc";
+//    return "";
+//  }
+  scoped_ptr_malloc<xmlChar, ScopedPtrXmlFree> str(
+      xmlEncodeEntitiesReentrant(NULL, ConstXMLStr(input.c_str())));
+  return string(reinterpret_cast<const char *>(str.get()));
+}
+
+UpdateCheckAction::UpdateCheckAction(const UpdateCheckParams& params,
+                                     HttpFetcher* http_fetcher)
+    : params_(params), http_fetcher_(http_fetcher) {}
+
+UpdateCheckAction::~UpdateCheckAction() {}
+
+void UpdateCheckAction::PerformAction() {
+  http_fetcher_->set_delegate(this);
+  string request_post(FormatRequest(params_));
+  http_fetcher_->SetPostData(request_post.data(), request_post.size());
+  http_fetcher_->BeginTransfer("https://tools.google.com/service/update2");
+}
+
+void UpdateCheckAction::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 UpdateCheckAction::ReceivedBytes(HttpFetcher *fetcher,
+                                   const char* bytes,
+                                   int length) {
+  response_buffer_.reserve(response_buffer_.size() + length);
+  response_buffer_.insert(response_buffer_.end(), bytes, bytes + length);
+}
+
+namespace {
+// 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), success_(false) {}
+  ~ScopedActionCompleter() {
+    processor_->ActionComplete(action_, success_);
+  }
+  void set_success(bool success) {
+    success_ = success;
+  }
+ private:
+  ActionProcessor* processor_;
+  AbstractAction* action_;
+  bool success_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
+};
+
+// If non-NULL response, caller is responsible for calling xmlXPathFreeObject()
+// on the returned object.
+// This code is roughly based on the libxml tutorial at:
+// http://xmlsoft.org/tutorial/apd.html
+xmlXPathObject* GetNodeSet(xmlDoc* doc, const xmlChar* xpath,
+                           const xmlChar* ns, const xmlChar* ns_url) {
+  xmlXPathObject* result = NULL;
+
+  scoped_ptr_malloc<xmlXPathContext, ScopedPtrXmlXPathContextFree> context(
+      xmlXPathNewContext(doc));
+  if (!context.get()) {
+    LOG(ERROR) << "xmlXPathNewContext() returned NULL";
+    return NULL;
+  }
+  if (xmlXPathRegisterNs(context.get(), ns, ns_url) < 0) {
+    LOG(ERROR) << "xmlXPathRegisterNs() returned error";
+    return NULL;
+  }
+
+  result = xmlXPathEvalExpression(xpath, context.get());
+
+  if (result == NULL) {
+    LOG(ERROR) << "xmlXPathEvalExpression returned error";
+    return NULL;
+  }
+  if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
+    LOG(INFO) << "xpath not found in doc";
+    xmlXPathFreeObject(result);
+    return NULL;
+  }
+  return result;
+}
+
+// Returns the string value of a named attribute on a node, or empty string
+// if no such node exists. If the attribute exists and has a value of
+// empty string, there's no way to distinguish that from the attribute
+// not existing.
+string XmlGetProperty(xmlNode* node, const char* name) {
+  if (!xmlHasProp(node, ConstXMLStr(name)))
+    return "";
+  scoped_ptr_malloc<xmlChar, ScopedPtrXmlFree> str(
+      xmlGetProp(node, ConstXMLStr(name)));
+  string ret(reinterpret_cast<const char *>(str.get()));
+  return ret;
+}
+
+// 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(), "%lld", &ret);
+  if (rc < 1) {
+    // failure
+    return 0;
+  }
+  return ret;
+}
+}  // namespace {}
+
+// If the transfer was successful, this uses libxml2 to parse the response
+// and fill in the appropriate fields of the output object. Also, notifies
+// the processor that we're done.
+void UpdateCheckAction::TransferComplete(HttpFetcher *fetcher,
+                                         bool successful) {
+  ScopedActionCompleter completer(processor_, this);
+  if (!successful)
+    return;
+  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_success(true);
+    return;
+  }
+
+  // parse our response and fill the fields in the output object
+  scoped_ptr_malloc<xmlDoc, ScopedPtrXmlDocFree> doc(
+      xmlParseMemory(&response_buffer_[0], response_buffer_.size()));
+  if (!doc.get()) {
+    LOG(ERROR) << "Omaha response not valid XML";
+    return;
+  }
+
+  static const char* kNamespace("x");
+  static const char* kUpdatecheckNodeXpath("/x:gupdate/x:app/x:updatecheck");
+  static const char* kNsUrl("http://www.google.com/update2/response");
+
+  scoped_ptr_malloc<xmlXPathObject, ScopedPtrXmlXPathObjectFree>
+      xpath_nodeset(GetNodeSet(doc.get(),
+                               ConstXMLStr(kUpdatecheckNodeXpath),
+                               ConstXMLStr(kNamespace),
+                               ConstXMLStr(kNsUrl)));
+  if (!xpath_nodeset.get()) {
+    return;
+  }
+  xmlNodeSet* nodeset = xpath_nodeset->nodesetval;
+  CHECK(nodeset) << "XPath missing NodeSet";
+  CHECK_GE(nodeset->nodeNr, 1);
+
+  xmlNode* updatecheck_node = nodeset->nodeTab[0];
+  // get status
+  if (!xmlHasProp(updatecheck_node, ConstXMLStr("status"))) {
+    LOG(ERROR) << "Response missing status";
+    return;
+  }
+
+  const string status(XmlGetProperty(updatecheck_node, "status"));
+  UpdateCheckResponse output_object;
+  if (status == "noupdate") {
+    LOG(INFO) << "No update.";
+    output_object.update_exists = false;
+    SetOutputObject(output_object);
+    completer.set_success(true);
+    return;
+  }
+
+  if (status != "ok") {
+    LOG(ERROR) << "Unknown status: " << status;
+    return;
+  }
+
+  // In best-effort fashion, fetch the rest of the expected attributes
+  // from the updatecheck node, then return the object
+  output_object.update_exists = true;
+  completer.set_success(true);
+
+  output_object.display_version =
+      XmlGetProperty(updatecheck_node, "DisplayVersion");
+  output_object.codebase = XmlGetProperty(updatecheck_node, "codebase");
+  output_object.more_info_url = XmlGetProperty(updatecheck_node, "MoreInfo");
+  output_object.hash = XmlGetProperty(updatecheck_node, "hash");
+  output_object.size = ParseInt(XmlGetProperty(updatecheck_node, "size"));
+  output_object.needs_admin =
+      XmlGetProperty(updatecheck_node, "needsadmin") == "true";
+  output_object.prompt = XmlGetProperty(updatecheck_node, "Prompt") == "true";
+  SetOutputObject(output_object);
+  return;
+}
+
+};  // namespace chromeos_update_engine
diff --git a/update_check_action.h b/update_check_action.h
new file mode 100644
index 0000000..cf6d512
--- /dev/null
+++ b/update_check_action.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_UPDATE_CHECK_ACTION_H__
+#define UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <string>
+
+#include <curl/curl.h>
+
+#include "base/scoped_ptr.h"
+#include "action.h"
+#include "http_fetcher.h"
+
+// The Update Check action makes an update check request to Omaha and
+// can output the response on the output ActionPipe.
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+// Encodes XML entities in a given string with libxml2. input must be
+// UTF-8 formatted. Output will be UTF-8 formatted.
+std::string XmlEncode(const std::string& input);
+
+// This struct encapsulates the data Omaha gets for the update check.
+// These strings in this struct should not be XML escaped.
+struct UpdateCheckParams {
+  UpdateCheckParams()
+      : os_platform(kOsPlatform), os_version(kOsVersion), app_id(kAppId) {}
+  UpdateCheckParams(const std::string& in_machine_id,
+                    const std::string& in_user_id,
+                    const std::string& in_os_platform,
+                    const std::string& in_os_version,
+                    const std::string& in_os_sp,
+                    const std::string& in_app_id,
+                    const std::string& in_app_version,
+                    const std::string& in_app_lang,
+                    const std::string& in_app_track)
+      : machine_id(in_machine_id),
+        user_id(in_user_id),
+        os_platform(in_os_platform),
+        os_version(in_os_version),
+        os_sp(in_os_sp),
+        app_id(in_app_id),
+        app_version(in_app_version),
+        app_lang(in_app_lang),
+        app_track(in_app_track) {}
+
+    std::string machine_id;
+    std::string user_id;
+    std::string os_platform;
+    std::string os_version;
+    std::string os_sp;
+    std::string app_id;
+    std::string app_version;
+    std::string app_lang;
+    std::string app_track;
+
+  // Suggested defaults
+  static const char* const kAppId;
+  static const char* const kOsPlatform;
+  static const char* const kOsVersion;
+};
+
+// This struct encapsulates the data Omaha returns for the update check.
+// These strings in this struct are not XML escaped.
+struct UpdateCheckResponse {
+  UpdateCheckResponse()
+      : update_exists(false), size(0), needs_admin(false), prompt(false) {}
+  // True iff there is an update to be downloaded.
+  bool update_exists;
+
+  // These are only valid if update_exists is true:
+  std::string display_version;
+  std::string codebase;
+  std::string more_info_url;
+  std::string hash;
+  off_t size;
+  bool needs_admin;
+  bool prompt;
+};
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64bit);
+
+class UpdateCheckAction;
+class NoneType;
+
+template<>
+class ActionTraits<UpdateCheckAction> {
+ public:
+  // Does not take an object for input
+  typedef NoneType InputObjectType;
+  // On success, puts the output path on output
+  typedef UpdateCheckResponse OutputObjectType;
+};
+
+class UpdateCheckAction : public Action<UpdateCheckAction>,
+                          public HttpFetcherDelegate {
+ public:
+  // 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.
+  // A good calling pattern is:
+  // UpdateCheckAction(..., new WhateverHttpFetcher);
+  UpdateCheckAction(const UpdateCheckParams& params,
+                    HttpFetcher* http_fetcher);
+  virtual ~UpdateCheckAction();
+  typedef ActionTraits<UpdateCheckAction>::InputObjectType InputObjectType;
+  typedef ActionTraits<UpdateCheckAction>::OutputObjectType OutputObjectType;
+  void PerformAction();
+  void TerminateProcessing();
+
+  // Debugging/logging
+  std::string Type() const { return "UpdateCheckAction"; }
+
+  // Delegate methods (see http_fetcher.h)
+  virtual void ReceivedBytes(HttpFetcher *fetcher,
+                             const char* bytes, int length);
+  virtual void TransferComplete(HttpFetcher *fetcher, bool successful);
+
+ private:
+  // These are data that are passed in the request to the Omaha server
+  UpdateCheckParams params_;
+
+  // pointer to the HttpFetcher that does the http work
+  scoped_ptr<HttpFetcher> http_fetcher_;
+
+  // Stores the response from the omaha server
+  std::vector<char> response_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateCheckAction);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_UPDATE_CHECK_ACTION_H__
diff --git a/update_check_action_unittest.cc b/update_check_action_unittest.cc
new file mode 100644
index 0000000..9b8436c
--- /dev/null
+++ b/update_check_action_unittest.cc
@@ -0,0 +1,492 @@
+// 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 <string>
+#include <glib.h>
+#include <gtest/gtest.h>
+#include "update_engine/action_pipe.h"
+#include "update_engine/update_check_action.h"
+#include "update_engine/mock_http_fetcher.h"
+#include "update_engine/omaha_hash_calculator.h"
+#include "update_engine/test_utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+
+class UpdateCheckActionTest : public ::testing::Test { };
+
+namespace {
+string GetNoUpdateResponse(const string& app_id) {
+  return string(
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
+      "xmlns=\"http://www.google.com/update2/response\" protocol=\"2.0\"><app "
+      "appid=\"") + app_id + "\" status=\"ok\"><ping "
+      "status=\"ok\"/><updatecheck status=\"noupdate\"/></app></gupdate>";
+}
+
+string GetUpdateResponse(const string& app_id,
+                         const string& display_version,
+                         const string& more_info_url,
+                         const string& prompt,
+                         const string& codebase,
+                         const string& hash,
+                         const string& needsadmin,
+                         const string& size) {
+  return string("<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
+      "xmlns=\"http://www.google.com/update2/response\" protocol=\"2.0\"><app "
+      "appid=\"") + app_id + "\" status=\"ok\"><ping "
+      "status=\"ok\"/><updatecheck DisplayVersion=\"" + display_version + "\" "
+      "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
+      "codebase=\"" + codebase + "\" "
+      "hash=\"" + hash + "\" needsadmin=\"" + needsadmin + "\" "
+      "size=\"" + size + "\" status=\"ok\"/></app></gupdate>";
+}
+
+class UpdateCheckActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  UpdateCheckActionTestProcessorDelegate()
+      : loop_(NULL),
+        expected_success_(true) {}
+  virtual ~UpdateCheckActionTestProcessorDelegate() {
+  }
+  virtual void ProcessingDone(const ActionProcessor* processor) {
+    ASSERT_TRUE(loop_);
+    g_main_loop_quit(loop_);
+  }
+
+  virtual void ActionCompleted(const ActionProcessor* processor,
+                               const AbstractAction* action,
+                               bool success) {
+    // make sure actions always succeed
+    if (action->Type() == "UpdateCheckAction")
+      EXPECT_EQ(expected_success_, success);
+    else
+      EXPECT_TRUE(success);
+  }
+  GMainLoop *loop_;
+  bool expected_success_;
+};
+
+gboolean StartProcessorInRunLoop(gpointer data) {
+  ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+  processor->StartProcessing();
+  return FALSE;
+}
+
+}  // namespace {}
+
+class OutputObjectCollectorAction;
+
+template<>
+class ActionTraits<OutputObjectCollectorAction> {
+ public:
+  // Does not take an object for input
+  typedef UpdateCheckResponse InputObjectType;
+  // On success, puts the output path on output
+  typedef NoneType OutputObjectType;
+};
+
+class OutputObjectCollectorAction : public Action<OutputObjectCollectorAction> {
+ public:
+  void PerformAction() {
+    // copy input object
+    has_input_object_ = HasInputObject();
+    if (has_input_object_)
+      update_check_response_ = GetInputObject();
+    processor_->ActionComplete(this, true);
+  }
+  // Should never be called
+  void TerminateProcessing() {
+    CHECK(false);
+  }
+  // Debugging/logging
+  std::string Type() const { return "OutputObjectCollectorAction"; }
+  bool has_input_object_;
+  UpdateCheckResponse update_check_response_;
+};
+
+// returns true iff an output response was obtained from the
+// UpdateCheckAction. out_response may be NULL.
+// out_post_data may be null; if non-null, the post-data received by the
+// mock HttpFetcher is returned.
+bool TestUpdateCheckAction(const UpdateCheckParams& params,
+                           const string& http_response,
+                           bool expected_success,
+                           UpdateCheckResponse* out_response,
+                           vector<char> *out_post_data) {
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+  MockHttpFetcher *fetcher = new MockHttpFetcher(http_response.data(),
+                                                 http_response.size());
+
+  UpdateCheckAction action(params, fetcher);  // takes ownership of fetcher
+  UpdateCheckActionTestProcessorDelegate delegate;
+  delegate.loop_ = loop;
+  delegate.expected_success_ = expected_success;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  OutputObjectCollectorAction collector_action;
+
+  BondActions(&action, &collector_action);
+  processor.EnqueueAction(&collector_action);
+
+  g_timeout_add(0, &StartProcessorInRunLoop, &processor);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+  if (collector_action.has_input_object_ && out_response)
+    *out_response = collector_action.update_check_response_;
+  if (out_post_data)
+    *out_post_data = fetcher->post_data();
+  return collector_action.has_input_object_;
+}
+
+TEST(UpdateCheckActionTest, NoUpdateTest) {
+  UpdateCheckParams params("",  // machine_id
+                           "",  // user_id
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "",  // os_sp
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheckAction(params,
+                            GetNoUpdateResponse(UpdateCheckParams::kAppId),
+                            true,
+                            &response,
+                            NULL));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST(UpdateCheckActionTest, ValidUpdateTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheckAction(params,
+                            GetUpdateResponse(UpdateCheckParams::kAppId,
+                                              "1.2.3.4",  // version
+                                              "http://more/info",
+                                              "true",  // prompt
+                                              "http://code/base",  // dl url
+                                              "HASH1234=",  // checksum
+                                              "false",  // needs admin
+                                              "123"),  // size
+                            true,
+                            &response,
+                            NULL));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ("1.2.3.4", response.display_version);
+  EXPECT_EQ("http://code/base", response.codebase);
+  EXPECT_EQ("http://more/info", response.more_info_url);
+  EXPECT_EQ("HASH1234=", response.hash);
+  EXPECT_EQ(123, response.size);
+  EXPECT_FALSE(response.needs_admin);
+  EXPECT_TRUE(response.prompt);
+}
+
+TEST(UpdateCheckActionTest, NoOutputPipeTest) {
+  UpdateCheckParams params("",  // machine_id
+                           "",  // usr_id
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "",  // os_sp
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest");
+  const string http_response(GetNoUpdateResponse(UpdateCheckParams::kAppId));
+
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  UpdateCheckAction action(params,
+                           new MockHttpFetcher(http_response.data(),
+                                               http_response.size()));
+  UpdateCheckActionTestProcessorDelegate delegate;
+  delegate.loop_ = loop;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  g_timeout_add(0, &StartProcessorInRunLoop, &processor);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+  EXPECT_FALSE(processor.IsRunning());
+}
+
+TEST(UpdateCheckActionTest, InvalidXmlTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheckAction(params,
+                            "invalid xml>",
+                            false,
+                            &response,
+                            NULL));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST(UpdateCheckActionTest, MissingStatusTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(TestUpdateCheckAction(
+      params,
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
+      "xmlns=\"http://www.google.com/update2/response\" protocol=\"2.0\"><app "
+      "appid=\"foo\" status=\"ok\"><ping "
+      "status=\"ok\"/><updatecheck/></app></gupdate>",
+      false,
+      &response,
+      NULL));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST(UpdateCheckActionTest, InvalidStatusTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(TestUpdateCheckAction(
+      params,
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
+      "xmlns=\"http://www.google.com/update2/response\" protocol=\"2.0\"><app "
+      "appid=\"foo\" status=\"ok\"><ping "
+      "status=\"ok\"/><updatecheck status=\"foo\"/></app></gupdate>",
+      false,
+      &response,
+      NULL));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST(UpdateCheckActionTest, MissingNodesetTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(TestUpdateCheckAction(
+      params,
+      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
+      "xmlns=\"http://www.google.com/update2/response\" protocol=\"2.0\"><app "
+      "appid=\"foo\" status=\"ok\"><ping "
+      "status=\"ok\"/></app></gupdate>",
+      false,
+      &response,
+      NULL));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST(UpdateCheckActionTest, MissingFieldTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(TestUpdateCheckAction(params,
+      string("<?xml version=\"1.0\" encoding=\"UTF-8\"?><gupdate "
+          "xmlns=\"http://www.google.com/update2/response\" "
+          "protocol=\"2.0\"><app  appid=\"") +
+          UpdateCheckParams::kAppId + "\" status=\"ok\"><ping "
+          "status=\"ok\"/><updatecheck DisplayVersion=\"1.2.3.4\" "
+          "Prompt=\"false\" "
+          "codebase=\"http://code/base\" "
+          "hash=\"HASH1234=\" needsadmin=\"true\" "
+          "size=\"123\" status=\"ok\"/></app></gupdate>",
+      true,
+      &response,
+      NULL));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ("1.2.3.4", response.display_version);
+  EXPECT_EQ("http://code/base", response.codebase);
+  EXPECT_EQ("", response.more_info_url);
+  EXPECT_EQ("HASH1234=", response.hash);
+  EXPECT_EQ(123, response.size);
+  EXPECT_TRUE(response.needs_admin);
+  EXPECT_FALSE(response.prompt);
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  void ProcessingStopped(const ActionProcessor* processor) {
+    ASSERT_TRUE(loop_);
+    g_main_loop_quit(loop_);
+  }
+  GMainLoop *loop_;
+};
+
+gboolean TerminateTransferTestStarter(gpointer data) {
+  ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
+  processor->StartProcessing();
+  CHECK(processor->IsRunning());
+  processor->StopProcessing();
+  return FALSE;
+}
+}  // namespace {}
+
+TEST(UpdateCheckActionTest, TerminateTransferTest) {
+  UpdateCheckParams params("",  // machine_id
+                           "",  // usr_id
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "",  // os_sp
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest");
+  string http_response("doesn't matter");
+  GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
+
+  UpdateCheckAction action(params,
+                           new MockHttpFetcher(http_response.data(),
+                                               http_response.size()));
+  TerminateEarlyTestProcessorDelegate delegate;
+  delegate.loop_ = loop;
+  ActionProcessor processor;
+  processor.set_delegate(&delegate);
+  processor.EnqueueAction(&action);
+
+  g_timeout_add(0, &TerminateTransferTestStarter, &processor);
+  g_main_loop_run(loop);
+  g_main_loop_unref(loop);
+}
+
+TEST(UpdateCheckActionTest, XmlEncodeTest) {
+  EXPECT_EQ("ab", XmlEncode("ab"));
+  EXPECT_EQ("a&lt;b", XmlEncode("a<b"));
+  EXPECT_EQ("foo-&#x3A9;", XmlEncode("foo-\xce\xa9"));
+  EXPECT_EQ("&lt;&amp;&gt;", XmlEncode("<&>"));
+  EXPECT_EQ("&amp;lt;&amp;amp;&amp;gt;", XmlEncode("&lt;&amp;&gt;"));
+
+  vector<char> post_data;
+
+  // Make sure XML Encode is being called on the params
+  UpdateCheckParams params("testthemachine<id",
+                           "testtheuser_id&lt;",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "testtheservice_pack>",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheckAction(params,
+                            "invalid xml>",
+                            false,
+                            &response,
+                            &post_data));
+  // convert post_data to string
+  string post_str(&post_data[0], post_data.size());
+  EXPECT_NE(post_str.find("testthemachine&lt;id"), string::npos);
+  EXPECT_EQ(post_str.find("testthemachine<id"), string::npos);
+  EXPECT_NE(post_str.find("testtheuser_id&amp;lt;"), string::npos);
+  EXPECT_EQ(post_str.find("testtheuser_id&lt;"), string::npos);
+  EXPECT_NE(post_str.find("testtheservice_pack&gt;"), string::npos);
+  EXPECT_EQ(post_str.find("testtheservice_pack>"), string::npos);
+}
+
+TEST(UpdateCheckActionTest, XmlDecodeTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheckAction(params,
+                            GetUpdateResponse(UpdateCheckParams::kAppId,
+                                              "1.2.3.4",  // version
+                                              "testthe&lt;url",  // more info
+                                              "true",  // prompt
+                                              "testthe&amp;codebase",  // dl url
+                                              "HASH1234=", // checksum
+                                              "false",  // needs admin
+                                              "123"),  // size
+                            true,
+                            &response,
+                            NULL));
+
+  EXPECT_EQ(response.more_info_url, "testthe<url");
+  EXPECT_EQ(response.codebase, "testthe&codebase");
+}
+
+TEST(UpdateCheckActionTest, ParseIntTest) {
+  UpdateCheckParams params("machine_id",
+                           "user_id",
+                           UpdateCheckParams::kOsPlatform,
+                           UpdateCheckParams::kOsVersion,
+                           "service_pack",
+                           UpdateCheckParams::kAppId,
+                           "0.1.0.0",
+                           "en-US",
+                           "unittest_track");
+  UpdateCheckResponse response;
+  ASSERT_TRUE(
+      TestUpdateCheckAction(params,
+                            GetUpdateResponse(UpdateCheckParams::kAppId,
+                                              "1.2.3.4",  // version
+                                              "theurl",  // more info
+                                              "true",  // prompt
+                                              "thecodebase",  // dl url
+                                              "HASH1234=", // checksum
+                                              "false",  // needs admin
+                                              // overflows int32:
+                                              "123123123123123"),  // size
+                            true,
+                            &response,
+                            NULL));
+
+  EXPECT_EQ(response.size, 123123123123123ll);
+}
+
+}  // namespace chromeos_update_engine