AU: MultiHttpFetcher cleanup/rewrite

This is the first of many CLs to cleanup/refactor/unfork the
HttpFetcher classes.

This CL changes MultiHttpFetcher to MultiRangeHTTPFetcher, makes it
work with a single base fetcher, and un-templatizes it.

Also, fix a (new?) bug in SConstruct w/ setting CCFLAGS.

TEST=unittests, tested an interrupted/resumed update on device.
BUG=10395

Review URL: http://codereview.chromium.org/5835004

Change-Id: I8422358a6d425233987dd799c5ee7c87135d85fd
diff --git a/SConstruct b/SConstruct
index 6b0646a..615d18c 100644
--- a/SConstruct
+++ b/SConstruct
@@ -230,7 +230,7 @@
 env.GlibMarshal('marshal.glibmarshal.c', 'marshal.list')
 
 if ARGUMENTS.get('debug', 0):
-  env['CCFLAGS'] += ' -fprofile-arcs -ftest-coverage'
+  env['CCFLAGS'] += ['-fprofile-arcs', '-ftest-coverage']
   env['LIBS'] += ['bz2', 'gcov']
 
 sources = Split("""action_processor.cc
@@ -258,6 +258,7 @@
                    libcurl_http_fetcher.cc
                    marshal.glibmarshal.c
                    metadata.cc
+                   multi_range_http_fetcher.cc
                    omaha_hash_calculator.cc
                    omaha_request_action.cc
                    omaha_request_params.cc
@@ -361,4 +362,4 @@
 unittest_cmd = unittest_env.Program('update_engine_unittests',
                            unittest_sources + unittest_main)
 Clean(unittest_cmd, Glob('*.gcda') + Glob('*.gcno') + Glob('*.gcov') +
-                    Split('html app.info'))
\ No newline at end of file
+                    Split('html app.info'))
diff --git a/http_fetcher.h b/http_fetcher.h
index a50c760..e4f791a 100644
--- a/http_fetcher.h
+++ b/http_fetcher.h
@@ -86,6 +86,12 @@
   virtual void set_idle_seconds(int seconds) {}
   virtual void set_retry_seconds(int seconds) {}
 
+  ProxyResolver* proxy_resolver() const { return proxy_resolver_; }
+
+  // These are used for testing:
+  virtual void SetConnectionAsExpensive(bool is_expensive) {}
+  virtual void SetBuildType(bool is_official) {}
+
  protected:
   // The URL we're actively fetching from
   std::string url_;
@@ -122,13 +128,11 @@
   // Called if the fetcher seeks to a particular offset.
   virtual void SeekToOffset(off_t offset) {}
 
-  // Called when the transfer has completed successfully or been aborted through
-  // means other than TerminateTransfer. It's OK to destroy the |fetcher| object
-  // in this callback.
+  // When a transfer has completed, exactly one of these two methods will be
+  // called. TransferTerminated is called when the transfer has been aborted
+  // through TerminateTransfer. TransferComplete is called in all other
+  // situations. It's OK to destroy the |fetcher| object in this callback.
   virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
-
-  // Called when the transfer has been aborted through TerminateTransfer. It's
-  // OK to destroy the |fetcher| object in this callback.
   virtual void TransferTerminated(HttpFetcher* fetcher) {}
 };
 
diff --git a/http_fetcher_unittest.cc b/http_fetcher_unittest.cc
index a42bb3b..ccda0b6 100644
--- a/http_fetcher_unittest.cc
+++ b/http_fetcher_unittest.cc
@@ -16,10 +16,11 @@
 
 #include "update_engine/libcurl_http_fetcher.h"
 #include "update_engine/mock_http_fetcher.h"
-#include "update_engine/multi_http_fetcher.h"
+#include "update_engine/multi_range_http_fetcher.h"
 #include "update_engine/proxy_resolver.h"
 
 using std::make_pair;
+using std::pair;
 using std::string;
 using std::vector;
 
@@ -170,16 +171,16 @@
 };
 
 template <>
-class HttpFetcherTest<MultiHttpFetcher<LibcurlHttpFetcher> >
+class HttpFetcherTest<MultiRangeHTTPFetcher>
     : public HttpFetcherTest<LibcurlHttpFetcher> {
  public:
   HttpFetcher* NewLargeFetcher() {
-    MultiHttpFetcher<LibcurlHttpFetcher> *ret =
-        new MultiHttpFetcher<LibcurlHttpFetcher>(
-            reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
-    MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect
-        ranges(1, make_pair(0, -1));
-    ret->set_ranges(ranges);
+    ProxyResolver* resolver =
+        reinterpret_cast<ProxyResolver*>(&proxy_resolver_);
+    MultiRangeHTTPFetcher *ret =
+        new MultiRangeHTTPFetcher(new LibcurlHttpFetcher(resolver));
+    ret->ClearRanges();
+    ret->AddRange(0, -1);
     // Speed up test execution.
     ret->set_idle_seconds(1);
     ret->set_retry_seconds(1);
@@ -193,7 +194,7 @@
 
 typedef ::testing::Types<LibcurlHttpFetcher,
                          MockHttpFetcher,
-                         MultiHttpFetcher<LibcurlHttpFetcher> >
+                         MultiRangeHTTPFetcher>
 HttpFetcherTestTypes;
 TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
 
@@ -344,7 +345,7 @@
     callback_once_ = false;
     // |fetcher| can be destroyed during this callback.
     fetcher_.reset(NULL);
- }
+  }
   void TerminateTransfer() {
     CHECK(once_);
     once_ = false;
@@ -651,7 +652,7 @@
 
 void MultiTest(HttpFetcher* fetcher_in,
                const string& url,
-               const MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect& ranges,
+               const vector<pair<off_t, off_t> >& ranges,
                const string& expected_prefix,
                off_t expected_size,
                int expected_response_code) {
@@ -660,10 +661,15 @@
     MultiHttpFetcherTestDelegate delegate(expected_response_code);
     delegate.loop_ = loop;
     delegate.fetcher_.reset(fetcher_in);
-    MultiHttpFetcher<LibcurlHttpFetcher>* multi_fetcher =
-        dynamic_cast<MultiHttpFetcher<LibcurlHttpFetcher>*>(fetcher_in);
+    MultiRangeHTTPFetcher* multi_fetcher =
+        dynamic_cast<MultiRangeHTTPFetcher*>(fetcher_in);
     ASSERT_TRUE(multi_fetcher);
-    multi_fetcher->set_ranges(ranges);
+    multi_fetcher->ClearRanges();
+    for (vector<pair<off_t, off_t> >::const_iterator it = ranges.begin(),
+             e = ranges.end(); it != e; ++it) {
+               LOG(INFO) << "Adding range";
+      multi_fetcher->AddRange(it->first, it->second);
+    }
     multi_fetcher->SetConnectionAsExpensive(false);
     multi_fetcher->SetBuildType(false);
     multi_fetcher->set_delegate(&delegate);
@@ -687,7 +693,7 @@
   typename TestFixture::HttpServer server;
   ASSERT_TRUE(server.started_);
 
-  MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+  vector<pair<off_t, off_t> > ranges;
   ranges.push_back(make_pair(0, 25));
   ranges.push_back(make_pair(99, -1));
   MultiTest(this->NewLargeFetcher(),
@@ -704,7 +710,7 @@
   typename TestFixture::HttpServer server;
   ASSERT_TRUE(server.started_);
 
-  MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+  vector<pair<off_t, off_t> > ranges;
   ranges.push_back(make_pair(0, 24));
   MultiTest(this->NewLargeFetcher(),
             this->BigUrl(),
@@ -720,7 +726,7 @@
   typename TestFixture::HttpServer server;
   ASSERT_TRUE(server.started_);
 
-  MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+  vector<pair<off_t, off_t> > ranges;
   ranges.push_back(make_pair(kBigSize - 2, -1));
   ranges.push_back(make_pair(kBigSize - 3, -1));
   MultiTest(this->NewLargeFetcher(),
@@ -737,9 +743,10 @@
   typename TestFixture::HttpServer server;
   ASSERT_TRUE(server.started_);
 
-  MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+  vector<pair<off_t, off_t> > ranges;
   ranges.push_back(make_pair(kBigSize - 2, 4));
   for (int i = 0; i < 2; ++i) {
+    LOG(INFO) << "i = " << i;
     MultiTest(this->NewLargeFetcher(),
               this->BigUrl(),
               ranges,
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
index cdc7523..16d0239 100644
--- a/libcurl_http_fetcher.cc
+++ b/libcurl_http_fetcher.cc
@@ -156,6 +156,7 @@
   retry_count_ = 0;
   no_network_retry_count_ = 0;
   http_response_code_ = 0;
+  terminate_requested_ = false;
   ResolveProxiesForUrl(url);
   ResumeTransfer(url);
   CurlPerformOnce();
diff --git a/multi_http_fetcher.h b/multi_http_fetcher.h
deleted file mode 100644
index 3d5ee11..0000000
--- a/multi_http_fetcher.h
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_HTTP_FETCHER_H__
-#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_HTTP_FETCHER_H__
-
-#include <deque>
-#include <tr1/memory>
-#include <utility>
-#include <vector>
-
-#include "update_engine/http_fetcher.h"
-#include "update_engine/utils.h"
-
-// This class is a simple wrapper around an HttpFetcher. The client
-// specifies a vector of byte ranges. MultiHttpFetcher will fetch bytes
-// from those offsets. Pass -1 as a length to specify unlimited length.
-// It really only would make sense for the last range specified to have
-// unlimited length.
-
-namespace chromeos_update_engine {
-
-template<typename BaseHttpFetcher>
-class MultiHttpFetcher : public HttpFetcher, public HttpFetcherDelegate {
- public:
-  typedef std::vector<std::pair<off_t, off_t> > RangesVect;
-  typedef std::vector<std::tr1::shared_ptr<BaseHttpFetcher> > FetchersVect;
-
-  MultiHttpFetcher(ProxyResolver* proxy_resolver)
-      : HttpFetcher(proxy_resolver),
-        sent_transfer_complete_(false),
-        pending_next_fetcher_(false),
-        current_index_(0),
-        bytes_received_this_fetcher_(0) {}
-  ~MultiHttpFetcher() {}
-
-  void set_ranges(const RangesVect& ranges) {
-    ranges_ = ranges;
-    fetchers_.resize(ranges_.size());  // Allocate the fetchers
-    for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher>
-             >::iterator it = fetchers_.begin(), e = fetchers_.end();
-         it != e; ++it) {
-      (*it) = std::tr1::shared_ptr<BaseHttpFetcher>(
-          new BaseHttpFetcher(proxy_resolver_));
-      (*it)->set_delegate(this);
-    }
-    LOG(INFO) << "done w/ list";
-  }
-
-  void SetOffset(off_t offset) {}  // for now, doesn't support this
-
-  // Begins the transfer to the specified URL.
-  void BeginTransfer(const std::string& url) {
-    url_ = url;
-    if (ranges_.empty()) {
-      if (delegate_)
-        delegate_->TransferComplete(this, true);
-      return;
-    }
-    current_index_ = 0;
-    LOG(INFO) << "starting first transfer";
-    StartTransfer();
-  }
-
-  void TransferTerminated(HttpFetcher* fetcher) {
-    LOG(INFO) << "Received transfer terminated.";
-    if (pending_next_fetcher_) {
-      pending_next_fetcher_ = false;
-      if (++current_index_ >= ranges_.size()) {
-        SendTransferComplete(fetcher, true);
-      } else {
-        StartTransfer();
-      }
-      return;
-    }
-    current_index_ = ranges_.size();
-    sent_transfer_complete_ = true;  // a fib
-    if (delegate_) {
-      // Note that after the callback returns this object may be destroyed.
-      delegate_->TransferTerminated(this);
-    }
-  }
-
-  void TerminateTransfer() {
-    // If the current fetcher is already being terminated, just wait for its
-    // TransferTerminated callback.
-    if (pending_next_fetcher_) {
-      pending_next_fetcher_ = false;
-      return;
-    }
-    // If there's a current active fetcher terminate it and wait for its
-    // TransferTerminated callback.
-    if (current_index_ < fetchers_.size()) {
-      fetchers_[current_index_]->TerminateTransfer();
-      return;
-    }
-    // Transfer is terminated before it got started and before any ranges were
-    // added.
-    TransferTerminated(this);
-  }
-
-  void Pause() {
-    if (current_index_ < fetchers_.size())
-      fetchers_[current_index_]->Pause();
-  }
-
-  void Unpause() {
-    if (current_index_ < fetchers_.size())
-      fetchers_[current_index_]->Unpause();
-  }
-
-  // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
-  void set_idle_seconds(int seconds) {
-    for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
-             it = fetchers_.begin(),
-             e = fetchers_.end(); it != e; ++it) {
-      (*it)->set_idle_seconds(seconds);
-    }
-  }
-  void set_retry_seconds(int seconds) {
-    for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
-             it = fetchers_.begin(),
-             e = fetchers_.end(); it != e; ++it) {
-      (*it)->set_retry_seconds(seconds);
-    }
-  }
-  void SetConnectionAsExpensive(bool is_expensive) {
-    for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
-             it = fetchers_.begin(),
-             e = fetchers_.end(); it != e; ++it) {
-      (*it)->SetConnectionAsExpensive(is_expensive);
-    }
-  }
-  void SetBuildType(bool is_official) {
-    for (typename std::vector<std::tr1::shared_ptr<BaseHttpFetcher> >::iterator
-             it = fetchers_.begin(),
-             e = fetchers_.end(); it != e; ++it) {
-      (*it)->SetBuildType(is_official);
-    }
-  }
-  
-  virtual void SetProxies(const std::deque<std::string>& proxies) {
-    for (typename FetchersVect::iterator it = fetchers_.begin(),
-             e = fetchers_.end(); it != e; ++it) {
-      (*it)->SetProxies(proxies);
-    }
-  }
-
- private:
-  void SendTransferComplete(HttpFetcher* fetcher, bool successful) {
-    if (sent_transfer_complete_)
-      return;
-    LOG(INFO) << "Sending transfer complete";
-    sent_transfer_complete_ = true;
-    http_response_code_ = fetcher->http_response_code();
-    if (delegate_)
-      delegate_->TransferComplete(this, successful);
-  }
-
-  void StartTransfer() {
-    if (current_index_ >= ranges_.size()) {
-      return;
-    }
-    LOG(INFO) << "Starting a transfer @" << ranges_[current_index_].first << "("
-              << ranges_[current_index_].second << ")";
-    bytes_received_this_fetcher_ = 0;
-    fetchers_[current_index_]->SetOffset(ranges_[current_index_].first);
-    if (delegate_)
-      delegate_->SeekToOffset(ranges_[current_index_].first);
-    fetchers_[current_index_]->BeginTransfer(url_);
-  }
-
-  void ReceivedBytes(HttpFetcher* fetcher, const char* bytes, int length) {
-    TEST_AND_RETURN(current_index_ < ranges_.size());
-    TEST_AND_RETURN(fetcher == fetchers_[current_index_].get());
-    TEST_AND_RETURN(!pending_next_fetcher_);
-    off_t next_size = length;
-    if (ranges_[current_index_].second >= 0) {
-      next_size = std::min(next_size,
-                           ranges_[current_index_].second -
-                           bytes_received_this_fetcher_);
-    }
-    LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
-    if (delegate_) {
-      delegate_->ReceivedBytes(this, bytes, next_size);
-    }
-    bytes_received_this_fetcher_ += length;
-    if (ranges_[current_index_].second >= 0 &&
-        bytes_received_this_fetcher_ >= ranges_[current_index_].second) {
-      // Terminates the current fetcher. Waits for its TransferTerminated
-      // callback before starting the next fetcher so that we don't end up
-      // signalling the delegate that the whole multi-transfer is complete
-      // before all fetchers are really done and cleaned up.
-      pending_next_fetcher_ = true;
-      fetcher->TerminateTransfer();
-    }
-  }
-
-  void TransferComplete(HttpFetcher* fetcher, bool successful) {
-    TEST_AND_RETURN(!pending_next_fetcher_);
-    LOG(INFO) << "Received transfer complete.";
-    if (current_index_ >= ranges_.size()) {
-      SendTransferComplete(fetcher, true);
-      return;
-    }
-    if (ranges_[current_index_].second < 0) {
-      // We're done with the current operation
-      current_index_++;
-      if (current_index_ >= ranges_.size() || !successful) {
-        SendTransferComplete(fetcher, successful);
-      } else {
-        // Do the next transfer
-        StartTransfer();
-      }
-      return;
-    }
-    if (bytes_received_this_fetcher_ < ranges_[current_index_].second) {
-      LOG(WARNING) << "Received insufficient bytes from fetcher. "
-                   << "Ending early";
-      SendTransferComplete(fetcher, false);
-      return;
-    }
-    LOG(INFO) << "Got spurious TransferComplete. Ignoring.";
-  }
-
-  // If true, do not send any more data or TransferComplete to the delegate.
-  bool sent_transfer_complete_;
-
-  // If true, the next fetcher needs to be started when TransferTerminated is
-  // received from the current fetcher.
-  bool pending_next_fetcher_;
-
-  RangesVect ranges_;
-  FetchersVect fetchers_;
-
-  RangesVect::size_type current_index_;  // index into ranges_, fetchers_
-  off_t bytes_received_this_fetcher_;
-
-  DISALLOW_COPY_AND_ASSIGN(MultiHttpFetcher);
-};
-
-}  // namespace chromeos_update_engine
-
-#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_HTTP_FETCHER_H__
diff --git a/multi_range_http_fetcher.cc b/multi_range_http_fetcher.cc
new file mode 100644
index 0000000..16d2c09
--- /dev/null
+++ b/multi_range_http_fetcher.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/multi_range_http_fetcher.h"
+
+#include "update_engine/utils.h"
+
+namespace chromeos_update_engine {
+
+// Begins the transfer to the specified URL.
+// State change: Stopped -> Downloading
+// (corner case: Stopped -> Stopped for an empty request)
+void MultiRangeHTTPFetcher::BeginTransfer(const std::string& url) {
+  CHECK(!base_fetcher_active_) << "BeginTransfer but already active.";
+  CHECK(!pending_transfer_ended_) << "BeginTransfer but pending.";
+  CHECK(!terminating_) << "BeginTransfer but terminating.";
+
+  if (ranges_.empty()) {
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferComplete(this, true);
+    return;
+  }
+  url_ = url;
+  current_index_ = 0;
+  bytes_received_this_range_ = 0;
+  LOG(INFO) << "starting first transfer";
+  base_fetcher_->set_delegate(this);
+  StartTransfer();
+}
+
+// State change: Downloading -> Pending transfer ended
+void MultiRangeHTTPFetcher::TerminateTransfer() {
+  if (!base_fetcher_active_) {
+    LOG(INFO) << "Called TerminateTransfer but not active.";
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferTerminated(this);
+    return;
+  }
+  terminating_ = true;
+    
+  if (!pending_transfer_ended_) {
+    base_fetcher_->TerminateTransfer();
+  }
+}
+
+// State change: Stopped or Downloading -> Downloading
+void MultiRangeHTTPFetcher::StartTransfer() {
+  if (current_index_ >= ranges_.size()) {
+    return;
+  }
+  LOG(INFO) << "Starting a transfer @" << ranges_[current_index_].first << "("
+            << ranges_[current_index_].second << ")";
+  bytes_received_this_range_ = 0;
+  base_fetcher_->SetOffset(ranges_[current_index_].first);
+  if (delegate_)
+    delegate_->SeekToOffset(ranges_[current_index_].first);
+  base_fetcher_active_ = true;
+  base_fetcher_->BeginTransfer(url_);
+}
+
+// State change: Downloading -> Downloading or Pending transfer ended
+void MultiRangeHTTPFetcher::ReceivedBytes(HttpFetcher* fetcher,
+                                          const char* bytes,
+                                          int length) {
+  CHECK_LT(current_index_, ranges_.size());
+  CHECK_EQ(fetcher, base_fetcher_.get());
+  CHECK(!pending_transfer_ended_);
+  off_t next_size = length;
+  if (ranges_[current_index_].second >= 0) {
+    next_size = std::min(next_size,
+                         ranges_[current_index_].second -
+                         bytes_received_this_range_);
+  }
+  LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
+  if (delegate_) {
+    delegate_->ReceivedBytes(this, bytes, next_size);
+  }
+  bytes_received_this_range_ += length;
+  if (ranges_[current_index_].second >= 0 &&
+      bytes_received_this_range_ >= ranges_[current_index_].second) {
+    // Terminates the current fetcher. Waits for its TransferTerminated
+    // callback before starting the next range so that we don't end up
+    // signalling the delegate that the whole multi-transfer is complete
+    // before all fetchers are really done and cleaned up.
+    pending_transfer_ended_ = true;
+    LOG(INFO) << "terminating transfer";
+    fetcher->TerminateTransfer();
+  }
+}
+
+// State change: Downloading or Pending transfer ended -> Stopped
+void MultiRangeHTTPFetcher::TransferEnded(HttpFetcher* fetcher,
+                                          bool successful) {
+  CHECK(base_fetcher_active_) << "Transfer ended unexpectedly.";
+  CHECK_EQ(fetcher, base_fetcher_.get());
+  pending_transfer_ended_ = false;
+  http_response_code_ = fetcher->http_response_code();
+  LOG(INFO) << "TransferEnded w/ code " << http_response_code_;
+  if (terminating_) {
+    LOG(INFO) << "Terminating.";
+    Reset();
+    // Note that after the callback returns this object may be destroyed.
+    if (delegate_)
+      delegate_->TransferTerminated(this);
+    return;
+  }
+
+  // If we didn't get enough bytes, it's failure
+  if (ranges_[current_index_].second >= 0) {
+    if (bytes_received_this_range_ < ranges_[current_index_].second) {
+      // Failure
+      LOG(INFO) << "Didn't get enough bytes. Ending w/ failure.";
+      Reset();
+      // Note that after the callback returns this object may be destroyed.
+      if (delegate_)
+        delegate_->TransferComplete(this, false);
+      return;
+    }
+    // We got enough bytes and there were bytes specified, so this is success.
+    successful = true;
+  }
+
+  // If we have another fetcher, use that.
+  if (current_index_ + 1 < ranges_.size()) {
+    current_index_++;
+    LOG(INFO) << "Starting next transfer (" << current_index_ << ").";
+    StartTransfer();
+    return;
+  }
+
+  LOG(INFO) << "Done w/ all transfers";
+  Reset();
+  // Note that after the callback returns this object may be destroyed.
+  if (delegate_)
+    delegate_->TransferComplete(this, successful);
+}
+
+void MultiRangeHTTPFetcher::TransferComplete(HttpFetcher* fetcher,
+                                             bool successful) {
+  LOG(INFO) << "Received transfer complete.";
+  TransferEnded(fetcher, successful);
+}
+
+void MultiRangeHTTPFetcher::TransferTerminated(HttpFetcher* fetcher) {
+  LOG(INFO) << "Received transfer terminated.";
+  TransferEnded(fetcher, false);
+}
+
+void MultiRangeHTTPFetcher::Reset() {
+  base_fetcher_active_ = pending_transfer_ended_ = terminating_ = false;
+  current_index_ = 0;
+  bytes_received_this_range_ = 0;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/multi_range_http_fetcher.h b/multi_range_http_fetcher.h
new file mode 100644
index 0000000..8a54bde
--- /dev/null
+++ b/multi_range_http_fetcher.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H__
+#define CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H__
+
+#include <deque>
+#include <utility>
+#include <vector>
+
+#include <base/scoped_ptr.h>
+
+#include "update_engine/http_fetcher.h"
+
+// This class is a simple wrapper around an HttpFetcher. The client
+// specifies a vector of byte ranges. MultiRangeHTTPFetcher will fetch bytes
+// from those offsets, using the same bash fetcher for all ranges. Thus, the
+// fetcher must support beginning a transfter after one has stopped. Pass -1
+// as a length to specify unlimited length. It really only would make sense
+// for the last range specified to have unlimited length, tho it is legal for
+// other entries to have unlimited length.
+
+// There are three states a MultiRangeHTTPFetcher object will be in:
+// - Stopped (start state)
+// - Downloading
+// - Pending transfer ended
+// Various functions below that might change state indicate possible
+// state changes.
+
+namespace chromeos_update_engine {
+
+class MultiRangeHTTPFetcher : public HttpFetcher, public HttpFetcherDelegate {
+ public:
+  // Takes ownership of the passed in fetcher.
+  explicit MultiRangeHTTPFetcher(HttpFetcher* base_fetcher)
+      : HttpFetcher(base_fetcher->proxy_resolver()),
+        base_fetcher_(base_fetcher),
+        base_fetcher_active_(false),
+        pending_transfer_ended_(false),
+        terminating_(false),
+        current_index_(0),
+        bytes_received_this_range_(0) {}
+  ~MultiRangeHTTPFetcher() {}
+
+  void ClearRanges() { ranges_.clear(); }
+
+  void AddRange(off_t offset, off_t size) {
+    ranges_.push_back(std::make_pair(offset, size));
+  }
+
+  virtual void SetOffset(off_t offset) {}  // for now, doesn't support this
+
+  // Begins the transfer to the specified URL.
+  // State change: Stopped -> Downloading
+  // (corner case: Stopped -> Stopped for an empty request)
+  virtual void BeginTransfer(const std::string& url);
+
+  // State change: Downloading -> Pending transfer ended
+  virtual void TerminateTransfer();
+
+  virtual void Pause() { base_fetcher_->Pause(); }
+
+  virtual void Unpause() { base_fetcher_->Unpause(); }
+
+  // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
+  virtual void set_idle_seconds(int seconds) {
+    base_fetcher_->set_idle_seconds(seconds);
+  }
+  virtual void set_retry_seconds(int seconds) {
+    base_fetcher_->set_retry_seconds(seconds);
+  }
+  virtual void SetConnectionAsExpensive(bool is_expensive) {
+    base_fetcher_->SetConnectionAsExpensive(is_expensive);
+  }
+  virtual void SetBuildType(bool is_official) {
+    base_fetcher_->SetBuildType(is_official);
+  }
+  virtual void SetProxies(const std::deque<std::string>& proxies) {
+    base_fetcher_->SetProxies(proxies);
+  }
+
+ private:
+  // pair<offset, length>:
+  typedef std::vector<std::pair<off_t, off_t> > RangesVect;
+  
+  // State change: Stopped or Downloading -> Downloading
+  void StartTransfer();
+
+// State change: Downloading -> Downloading or Pending transfer ended
+  virtual void ReceivedBytes(HttpFetcher* fetcher,
+                             const char* bytes,
+                             int length);
+
+  // State change: Pending transfer ended -> Stopped
+  void TransferEnded(HttpFetcher* fetcher, bool successful);
+  // These two call TransferEnded():
+  virtual void TransferComplete(HttpFetcher* fetcher, bool successful);
+  virtual void TransferTerminated(HttpFetcher* fetcher);
+
+  void Reset();
+
+  scoped_ptr<HttpFetcher> base_fetcher_;
+
+  // If true, do not send any more data or TransferComplete to the delegate.
+  bool base_fetcher_active_;
+
+  // If true, the next fetcher needs to be started when TransferTerminated is
+  // received from the current fetcher.
+  bool pending_transfer_ended_;
+  
+  // True if we are waiting for base fetcher to terminate b/c we are
+  // ourselves terminating.
+  bool terminating_;
+
+  RangesVect ranges_;
+
+  RangesVect::size_type current_index_;  // index into ranges_
+  off_t bytes_received_this_range_;
+
+  DISALLOW_COPY_AND_ASSIGN(MultiRangeHTTPFetcher);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // CHROMEOS_PLATFORM_UPDATE_ENGINE_MULTI_RANGE_HTTP_FETCHER_H__
diff --git a/update_attempter.cc b/update_attempter.cc
index 85500e6..8139687 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -22,7 +22,7 @@
 #include "update_engine/download_action.h"
 #include "update_engine/filesystem_copier_action.h"
 #include "update_engine/libcurl_http_fetcher.h"
-#include "update_engine/multi_http_fetcher.h"
+#include "update_engine/multi_range_http_fetcher.h"
 #include "update_engine/omaha_request_action.h"
 #include "update_engine/omaha_request_params.h"
 #include "update_engine/omaha_response_handler_action.h"
@@ -185,8 +185,8 @@
                                  OmahaEvent::kTypeUpdateDownloadStarted),
                              new LibcurlHttpFetcher(GetProxyResolver())));
   shared_ptr<DownloadAction> download_action(
-      new DownloadAction(prefs_, new MultiHttpFetcher<LibcurlHttpFetcher>(
-          GetProxyResolver())));
+      new DownloadAction(prefs_, new MultiRangeHTTPFetcher(
+          new LibcurlHttpFetcher(GetProxyResolver()))));
   shared_ptr<OmahaRequestAction> download_finished_action(
       new OmahaRequestAction(prefs_,
                              omaha_request_params_,
@@ -554,15 +554,14 @@
 }
 
 void UpdateAttempter::SetupDownload() {
-  MultiHttpFetcher<LibcurlHttpFetcher>* fetcher =
-      dynamic_cast<MultiHttpFetcher<LibcurlHttpFetcher>*>(
-          download_action_->http_fetcher());
-  MultiHttpFetcher<LibcurlHttpFetcher>::RangesVect ranges;
+  MultiRangeHTTPFetcher* fetcher =
+      dynamic_cast<MultiRangeHTTPFetcher*>(download_action_->http_fetcher());
+  fetcher->ClearRanges();
   if (response_handler_action_->install_plan().is_resume) {
     // Resuming an update so fetch the update manifest metadata first.
     int64_t manifest_metadata_size = 0;
     prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
-    ranges.push_back(make_pair(0, manifest_metadata_size));
+    fetcher->AddRange(0, manifest_metadata_size);
     // If there're remaining unprocessed data blobs, fetch them. Be careful not
     // to request data beyond the end of the payload to avoid 416 HTTP response
     // error codes.
@@ -570,12 +569,11 @@
     prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset);
     uint64_t resume_offset = manifest_metadata_size + next_data_offset;
     if (resume_offset < response_handler_action_->install_plan().size) {
-      ranges.push_back(make_pair(resume_offset, -1));
+      fetcher->AddRange(resume_offset, -1);
     }
   } else {
-    ranges.push_back(make_pair(0, -1));
+    fetcher->AddRange(0, -1);
   }
-  fetcher->set_ranges(ranges);
 }
 
 }  // namespace chromeos_update_engine