p2p: Use p2p for updates

This is the main patch for enabling use of p2p for consuming and/or
sharing updates via p2p. Refer to the ddoc and other documentation for
how this works.

BUG=chromium:260426,chromium:273110
TEST=New unit tests + unit tests pass + manual testing
Change-Id: I6bc3bddae1e041ccc176969a651396e8e89cb3f0
Reviewed-on: https://chromium-review.googlesource.com/64829
Reviewed-by: David Zeuthen <zeuthen@chromium.org>
Commit-Queue: David Zeuthen <zeuthen@chromium.org>
Tested-by: David Zeuthen <zeuthen@chromium.org>
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 0e984c0..1c11a4b 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -9,6 +9,7 @@
 #include <sstream>
 #include <string>
 
+#include <base/bind.h>
 #include <base/logging.h>
 #include <base/rand_util.h>
 #include <base/string_number_conversions.h>
@@ -20,7 +21,9 @@
 
 #include "update_engine/action_pipe.h"
 #include "update_engine/constants.h"
+#include "update_engine/omaha_hash_calculator.h"
 #include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
 #include "update_engine/payload_state_interface.h"
 #include "update_engine/prefs_interface.h"
 #include "update_engine/utils.h"
@@ -47,6 +50,8 @@
 // Deprecated: "NeedsAdmin"
 static const char* kTagPrompt = "Prompt";
 static const char* kTagSha256 = "sha256";
+static const char* kTagDisableP2PForDownloading = "DisableP2PForDownloading";
+static const char* kTagDisableP2PForSharing = "DisableP2PForSharing";
 
 namespace {
 
@@ -490,10 +495,6 @@
   if (!ParseParams(doc, output_object, completer))
     return false;
 
-  output_object->update_exists = true;
-  SetOutputObject(*output_object);
-  completer->set_code(kErrorCodeSuccess);
-
   return true;
 }
 
@@ -693,6 +694,10 @@
   output_object->deadline = XmlGetProperty(pie_action_node, kTagDeadline);
   output_object->max_days_to_scatter =
       ParseInt(XmlGetProperty(pie_action_node, kTagMaxDaysToScatter));
+  output_object->disable_p2p_for_downloading =
+      (XmlGetProperty(pie_action_node, kTagDisableP2PForDownloading) == "true");
+  output_object->disable_p2p_for_sharing =
+      (XmlGetProperty(pie_action_node, kTagDisableP2PForSharing) == "true");
 
   string max = XmlGetProperty(pie_action_node, kTagMaxFailureCountPerUrl);
   if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
@@ -771,6 +776,8 @@
   OmahaResponse output_object;
   if (!ParseResponse(doc.get(), &output_object, &completer))
     return;
+  output_object.update_exists = true;
+  SetOutputObject(output_object);
 
   if (params_->update_disabled()) {
     LOG(INFO) << "Ignoring Omaha updates as updates are disabled by policy.";
@@ -786,11 +793,16 @@
     return;
   }
 
-  if (ShouldDeferDownload(&output_object)) {
-    output_object.update_exists = false;
-    LOG(INFO) << "Ignoring Omaha updates as updates are deferred by policy.";
-    completer.set_code(kErrorCodeOmahaUpdateDeferredPerPolicy);
-    return;
+  // If Omaha says to disable p2p, respect that
+  if (output_object.disable_p2p_for_downloading) {
+    LOG(INFO) << "Forcibly disabling use of p2p for downloading as "
+              << "requested by Omaha.";
+    params_->set_use_p2p_for_downloading(false);
+  }
+  if (output_object.disable_p2p_for_sharing) {
+    LOG(INFO) << "Forcibly disabling use of p2p for sharing as "
+              << "requested by Omaha.";
+    params_->set_use_p2p_for_sharing(false);
   }
 
   // Update the payload state with the current response. The payload state
@@ -802,13 +814,100 @@
   PayloadStateInterface* payload_state = system_state_->payload_state();
   payload_state->SetResponse(output_object);
 
-  if (payload_state->ShouldBackoffDownload()) {
+  // It could be we've already exceeded the deadline for when p2p is
+  // allowed or that we've tried too many times with p2p. Check that.
+  if (params_->use_p2p_for_downloading()) {
+    payload_state->P2PNewAttempt();
+    if (!payload_state->P2PAttemptAllowed()) {
+      LOG(INFO) << "Forcibly disabling use of p2p for downloading because "
+                << "of previous failures when using p2p.";
+      params_->set_use_p2p_for_downloading(false);
+    }
+  }
+
+  // From here on, we'll complete stuff in CompleteProcessing() so
+  // disable |completer| since we'll create a new one in that
+  // function.
+  completer.set_should_complete(false);
+
+  // If we're allowed to use p2p for downloading we do not pay
+  // attention to wall-clock-based waiting if the URL is indeed
+  // available via p2p. Therefore, check if the file is available via
+  // p2p before deferring...
+  if (params_->use_p2p_for_downloading()) {
+    LookupPayloadViaP2P(output_object);
+  } else {
+    CompleteProcessing();
+  }
+}
+
+void OmahaRequestAction::CompleteProcessing() {
+  ScopedActionCompleter completer(processor_, this);
+  OmahaResponse& output_object = const_cast<OmahaResponse&>(GetOutputObject());
+  PayloadStateInterface* payload_state = system_state_->payload_state();
+
+  if (ShouldDeferDownload(&output_object)) {
     output_object.update_exists = false;
-    LOG(INFO) << "Ignoring Omaha updates in order to backoff our retry "
-                 "attempts";
-    completer.set_code(kErrorCodeOmahaUpdateDeferredForBackoff);
+    LOG(INFO) << "Ignoring Omaha updates as updates are deferred by policy.";
+    completer.set_code(kErrorCodeOmahaUpdateDeferredPerPolicy);
     return;
   }
+
+  // Only back off if we're not using p2p
+  if (params_->use_p2p_for_downloading() && !params_->p2p_url().empty()) {
+    LOG(INFO) << "Payload backoff logic is disabled because download "
+              << "will happen from local peer (via p2p).";
+  } else {
+    if (payload_state->ShouldBackoffDownload()) {
+      output_object.update_exists = false;
+      LOG(INFO) << "Ignoring Omaha updates in order to backoff our retry "
+                << "attempts";
+      completer.set_code(kErrorCodeOmahaUpdateDeferredForBackoff);
+      return;
+    }
+  }
+
+  completer.set_code(kErrorCodeSuccess);
+}
+
+void OmahaRequestAction::OnLookupPayloadViaP2PCompleted(const string& url) {
+  LOG(INFO) << "Lookup complete, p2p-client returned URL '" << url << "'";
+  if (!url.empty()) {
+    params_->set_p2p_url(url);
+  } else {
+    LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+              << "because no suitable peer could be found.";
+    params_->set_use_p2p_for_downloading(false);
+  }
+  CompleteProcessing();
+}
+
+void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) {
+  // The kPrefsUpdateStateNextDataOffset state variable tracks the
+  // offset into the payload of the last completed operation if we're
+  // in the middle of an update. As such, p2p is only useful if the
+  // peer actually has that many bytes.
+  size_t minimum_size = 0;
+  int64_t next_data_offset = -1;
+  if (system_state_ != NULL &&
+      system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataOffset,
+                                       &next_data_offset)) {
+    if (next_data_offset > 0) {
+      minimum_size = next_data_offset;
+    }
+  }
+
+  string file_id = utils::CalculateP2PFileId(response.hash, response.size);
+  if (system_state_->p2p_manager() != NULL) {
+    LOG(INFO) << "Checking if payload is available via p2p, file_id="
+              << file_id << " minimum_size=" << minimum_size;
+    system_state_->p2p_manager()->LookupUrlForFile(
+        file_id,
+        minimum_size,
+        TimeDelta::FromHours(kMaxP2PNetworkWaitTimeSeconds),
+        base::Bind(&OmahaRequestAction::OnLookupPayloadViaP2PCompleted,
+                   base::Unretained(this)));
+  }
 }
 
 bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) {
@@ -817,6 +916,16 @@
     return false;
   }
 
+  // If we're using p2p to download _and_ we have a p2p URL, we never
+  // defer the download. This is because the download will always
+  // happen from a peer on the LAN and we've been waiting in line for
+  // our turn.
+  if (params_->use_p2p_for_downloading() && !params_->p2p_url().empty()) {
+    LOG(INFO) << "Download not deferred because download "
+              << "will happen from a local peer (via p2p).";
+    return false;
+  }
+
   // We should defer the downloads only if we've first satisfied the
   // wall-clock-based-waiting period and then the update-check-based waiting
   // period, if required.