Add support for long-poll HTTP request at /privet/v3/checkForUpdates

Added state/command definition notification mechanism through a long
poll HTTP request as described in:
https://developers.google.com/cloud-devices/v1/reference/local-api/check_for_updates

BUG: 23908251
Change-Id: I5285403f35f23a310404d3324a7a255b77211264
Reviewed-on: https://weave-review.googlesource.com/1533
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/examples/provider/event_http_server.cc b/examples/provider/event_http_server.cc
index bb55c50..e0ecea6 100644
--- a/examples/provider/event_http_server.cc
+++ b/examples/provider/event_http_server.cc
@@ -183,6 +183,10 @@
   return 7781;
 }
 
+base::TimeDelta HttpServerImpl::GetRequestTimeout() const {
+  return base::TimeDelta::Max();
+}
+
 std::vector<uint8_t> HttpServerImpl::GetHttpsCertificateFingerprint() const {
   return cert_fingerprint_;
 }
diff --git a/examples/provider/event_http_server.h b/examples/provider/event_http_server.h
index e51c6f4..950e536 100644
--- a/examples/provider/event_http_server.h
+++ b/examples/provider/event_http_server.h
@@ -34,6 +34,7 @@
                               const RequestHandlerCallback& callback) override;
   uint16_t GetHttpPort() const override;
   uint16_t GetHttpsPort() const override;
+  base::TimeDelta GetRequestTimeout() const override;
   std::vector<uint8_t> GetHttpsCertificateFingerprint() const override;
 
  private:
diff --git a/include/weave/provider/http_server.h b/include/weave/provider/http_server.h
index ced7975..622785b 100644
--- a/include/weave/provider/http_server.h
+++ b/include/weave/provider/http_server.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include <base/callback.h>
+#include <base/time/time.h>
 #include <weave/stream.h>
 
 namespace weave {
@@ -135,6 +136,10 @@
   virtual uint16_t GetHttpsPort() const = 0;
   virtual std::vector<uint8_t> GetHttpsCertificateFingerprint() const = 0;
 
+  // Specifies request timeout, after which the web server automatically aborts
+  // requests. Should return base::TimeDelta::Max() if there is no timeout.
+  virtual base::TimeDelta GetRequestTimeout() const = 0;
+
  protected:
   virtual ~HttpServer() = default;
 };
diff --git a/include/weave/provider/test/mock_http_server.h b/include/weave/provider/test/mock_http_server.h
index f56470b..995a8d8 100644
--- a/include/weave/provider/test/mock_http_server.h
+++ b/include/weave/provider/test/mock_http_server.h
@@ -25,6 +25,7 @@
   MOCK_CONST_METHOD0(GetHttpPort, uint16_t());
   MOCK_CONST_METHOD0(GetHttpsPort, uint16_t());
   MOCK_CONST_METHOD0(GetHttpsCertificateFingerprint, std::vector<uint8_t>());
+  MOCK_CONST_METHOD0(GetRequestTimeout, base::TimeDelta());
 };
 
 }  // namespace test
diff --git a/src/privet/device_delegate.cc b/src/privet/device_delegate.cc
index 78aa3c4..9a05483 100644
--- a/src/privet/device_delegate.cc
+++ b/src/privet/device_delegate.cc
@@ -5,6 +5,7 @@
 #include "src/privet/device_delegate.h"
 
 #include <base/guid.h>
+#include <weave/provider/task_runner.h>
 
 #include "src/privet/constants.h"
 
@@ -15,8 +16,12 @@
 
 class DeviceDelegateImpl : public DeviceDelegate {
  public:
-  DeviceDelegateImpl(uint16_t http_port, uint16_t https_port)
-      : http_port_{http_port}, https_port_{https_port} {}
+  DeviceDelegateImpl(provider::TaskRunner* task_runner,
+                     uint16_t http_port,
+                     uint16_t https_port,
+                     base::TimeDelta http_request_timeout)
+      : task_runner_{task_runner}, http_request_timeout_{http_request_timeout},
+        http_port_{http_port}, https_port_{https_port} {}
   ~DeviceDelegateImpl() override = default;
 
   std::pair<uint16_t, uint16_t> GetHttpEnpoint() const override {
@@ -29,11 +34,19 @@
     return base::Time::Now() - start_time_;
   }
 
-  void SetHttpPort(uint16_t port) override { http_port_ = port; }
+  base::TimeDelta GetHttpRequestTimeout() const override {
+    return http_request_timeout_;
+  }
 
-  void SetHttpsPort(uint16_t port) override { https_port_ = port; }
+  void PostDelayedTask(const tracked_objects::Location& from_here,
+                       const base::Closure& task,
+                       base::TimeDelta delay) override {
+    task_runner_->PostDelayedTask(from_here, task, delay);
+  }
 
  private:
+  provider::TaskRunner* task_runner_;
+  base::TimeDelta http_request_timeout_;
   uint16_t http_port_{0};
   uint16_t https_port_{0};
   base::Time start_time_{base::Time::Now()};
@@ -47,10 +60,13 @@
 
 // static
 std::unique_ptr<DeviceDelegate> DeviceDelegate::CreateDefault(
+    provider::TaskRunner* task_runner,
     uint16_t http_port,
-    uint16_t https_port) {
+    uint16_t https_port,
+    base::TimeDelta http_request_timeout) {
   return std::unique_ptr<DeviceDelegate>(
-      new DeviceDelegateImpl(http_port, https_port));
+      new DeviceDelegateImpl(task_runner, http_port, https_port,
+                             http_request_timeout));
 }
 
 }  // namespace privet
diff --git a/src/privet/device_delegate.h b/src/privet/device_delegate.h
index 3f13b22..1affa1b 100644
--- a/src/privet/device_delegate.h
+++ b/src/privet/device_delegate.h
@@ -8,9 +8,16 @@
 #include <memory>
 #include <utility>
 
+#include <base/callback.h>
+#include <base/location.h>
 #include <base/time/time.h>
 
 namespace weave {
+
+namespace provider {
+class TaskRunner;
+}
+
 namespace privet {
 
 // Interface to provide access to general information about device.
@@ -21,25 +28,31 @@
 
   // Returns HTTP ports for Privet. The first one is the primary port,
   // the second is the port for a pooling updates requests. The second value
-  // could be 0. In this case the first port would be use for regular and for
+  // could be 0. In this case the first port would be used for regular and for
   // updates requests.
   virtual std::pair<uint16_t, uint16_t> GetHttpEnpoint() const = 0;
 
   // The same |GetHttpEnpoint| but for HTTPS.
   virtual std::pair<uint16_t, uint16_t> GetHttpsEnpoint() const = 0;
 
-  // Returns device update.
+  // Returns device uptime.
   virtual base::TimeDelta GetUptime() const = 0;
 
-  // Updates the HTTP port value.
-  virtual void SetHttpPort(uint16_t port) = 0;
+  // Returns the max request timeout of http server. Returns TimeDelta::Max() if
+  // no timeout is set.
+  virtual base::TimeDelta GetHttpRequestTimeout() const = 0;
 
-  // Updates the HTTPS port value.
-  virtual void SetHttpsPort(uint16_t port) = 0;
+  // Schedules a background task on the embedded TaskRunner.
+  virtual void PostDelayedTask(const tracked_objects::Location& from_here,
+                               const base::Closure& task,
+                               base::TimeDelta delay) = 0;
 
   // Create default instance.
-  static std::unique_ptr<DeviceDelegate> CreateDefault(uint16_t http_port,
-                                                       uint16_t https_port);
+  static std::unique_ptr<DeviceDelegate> CreateDefault(
+      provider::TaskRunner* task_runner,
+      uint16_t http_port,
+      uint16_t https_port,
+      base::TimeDelta http_request_timeout);
 };
 
 }  // namespace privet
diff --git a/src/privet/mock_delegates.h b/src/privet/mock_delegates.h
index 755110e..2186f2f 100644
--- a/src/privet/mock_delegates.h
+++ b/src/privet/mock_delegates.h
@@ -45,8 +45,10 @@
   MOCK_CONST_METHOD0(GetHttpEnpoint, IntPair());
   MOCK_CONST_METHOD0(GetHttpsEnpoint, IntPair());
   MOCK_CONST_METHOD0(GetUptime, base::TimeDelta());
-  MOCK_METHOD1(SetHttpPort, void(uint16_t));
-  MOCK_METHOD1(SetHttpsPort, void(uint16_t));
+  MOCK_CONST_METHOD0(GetHttpRequestTimeout, base::TimeDelta());
+  MOCK_METHOD3(PostDelayedTask, void(const tracked_objects::Location&,
+                                     const base::Closure&,
+                                     base::TimeDelta));
 
   MockDeviceDelegate() {
     EXPECT_CALL(*this, GetHttpEnpoint())
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc
index d959f66..157c982 100644
--- a/src/privet/privet_handler.cc
+++ b/src/privet/privet_handler.cc
@@ -4,6 +4,7 @@
 
 #include "src/privet/privet_handler.h"
 
+#include <algorithm>
 #include <memory>
 #include <set>
 #include <string>
@@ -11,10 +12,10 @@
 
 #include <base/bind.h>
 #include <base/location.h>
-#include <base/strings/string_number_conversions.h>
 #include <base/strings/stringprintf.h>
 #include <base/values.h>
 #include <weave/enum_to_string.h>
+#include <weave/provider/task_runner.h>
 
 #include "src/http_constants.h"
 #include "src/privet/cloud_delegate.h"
@@ -104,6 +105,10 @@
 const char kCommandsKey[] = "commands";
 const char kCommandsIdKey[] = "id";
 
+const char kStateFingerprintKey[] = "stateFingerprint";
+const char kCommandsFingerprintKey[] = "commandsFingerprint";
+const char kWaitTimeoutKey[] = "waitTimeout";
+
 const char kInvalidParamValueFormat[] = "Invalid parameter: '%s'='%s'";
 
 const int kAccessTokenExpirationSeconds = 3600;
@@ -313,10 +318,12 @@
 }  // namespace
 
 std::vector<std::string> PrivetHandler::GetHttpPaths() const {
-  return {
-      "/privet/info", "/privet/v3/pairing/start", "/privet/v3/pairing/confirm",
-      "/privet/v3/pairing/cancel",
-  };
+  std::vector<std::string> result;
+  for (const auto& pair : handlers_) {
+    if (!pair.second.https_only)
+      result.push_back(pair.first);
+  }
+  return result;
 }
 
 std::vector<std::string> PrivetHandler::GetHttpsPaths() const {
@@ -343,33 +350,56 @@
              AuthScope::kNone);
   AddHandler("/privet/v3/pairing/cancel", &PrivetHandler::HandlePairingCancel,
              AuthScope::kNone);
-  AddHandler("/privet/v3/auth", &PrivetHandler::HandleAuth, AuthScope::kNone);
-  AddHandler("/privet/v3/setup/start", &PrivetHandler::HandleSetupStart,
-             AuthScope::kOwner);
-  AddHandler("/privet/v3/setup/status", &PrivetHandler::HandleSetupStatus,
-             AuthScope::kOwner);
-  AddHandler("/privet/v3/state", &PrivetHandler::HandleState,
-             AuthScope::kViewer);
-  AddHandler("/privet/v3/commandDefs", &PrivetHandler::HandleCommandDefs,
-             AuthScope::kViewer);
-  AddHandler("/privet/v3/commands/execute",
-             &PrivetHandler::HandleCommandsExecute, AuthScope::kViewer);
-  AddHandler("/privet/v3/commands/status", &PrivetHandler::HandleCommandsStatus,
-             AuthScope::kViewer);
-  AddHandler("/privet/v3/commands/cancel", &PrivetHandler::HandleCommandsCancel,
-             AuthScope::kViewer);
-  AddHandler("/privet/v3/commands/list", &PrivetHandler::HandleCommandsList,
-             AuthScope::kViewer);
+
+  AddSecureHandler("/privet/v3/auth", &PrivetHandler::HandleAuth,
+                   AuthScope::kNone);
+  AddSecureHandler("/privet/v3/setup/start", &PrivetHandler::HandleSetupStart,
+                   AuthScope::kOwner);
+  AddSecureHandler("/privet/v3/setup/status", &PrivetHandler::HandleSetupStatus,
+                   AuthScope::kOwner);
+  AddSecureHandler("/privet/v3/state", &PrivetHandler::HandleState,
+                   AuthScope::kViewer);
+  AddSecureHandler("/privet/v3/commandDefs", &PrivetHandler::HandleCommandDefs,
+                   AuthScope::kViewer);
+  AddSecureHandler("/privet/v3/commands/execute",
+                   &PrivetHandler::HandleCommandsExecute, AuthScope::kViewer);
+  AddSecureHandler("/privet/v3/commands/status",
+                   &PrivetHandler::HandleCommandsStatus, AuthScope::kViewer);
+  AddSecureHandler("/privet/v3/commands/cancel",
+                   &PrivetHandler::HandleCommandsCancel, AuthScope::kViewer);
+  AddSecureHandler("/privet/v3/commands/list",
+                   &PrivetHandler::HandleCommandsList, AuthScope::kViewer);
+  AddSecureHandler("/privet/v3/checkForUpdates",
+                   &PrivetHandler::HandleCheckForUpdates, AuthScope::kViewer);
 }
 
-PrivetHandler::~PrivetHandler() {}
+PrivetHandler::~PrivetHandler() {
+  for (const auto& req : update_requests_)
+    ReplyToUpdateRequest(req.callback);
+}
 
 void PrivetHandler::OnCommandDefsChanged() {
   ++command_defs_fingerprint_;
+  auto pred = [this](const UpdateRequestParameters& params) {
+    return params.command_defs_fingerprint < 0;
+  };
+  auto last =
+      std::partition(update_requests_.begin(), update_requests_.end(), pred);
+  for (auto p = last; p != update_requests_.end(); ++p)
+    ReplyToUpdateRequest(p->callback);
+  update_requests_.erase(last, update_requests_.end());
 }
 
 void PrivetHandler::OnStateChanged() {
   ++state_fingerprint_;
+  auto pred = [this](const UpdateRequestParameters& params) {
+    return params.state_fingerprint < 0;
+  };
+  auto last =
+      std::partition(update_requests_.begin(), update_requests_.end(), pred);
+  for (auto p = last; p != update_requests_.end(); ++p)
+    ReplyToUpdateRequest(p->callback);
+  update_requests_.erase(last, update_requests_.end());
 }
 
 void PrivetHandler::HandleRequest(const std::string& api,
@@ -422,20 +452,34 @@
     }
   }
 
-  if (handler->second.first > user_info.scope()) {
+  if (handler->second.scope > user_info.scope()) {
     Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
                        errors::kInvalidAuthorizationScope,
                        "Scope '%s' does not allow '%s'",
                        EnumToString(user_info.scope()).c_str(), api.c_str());
     return ReturnError(*error, callback);
   }
-  (this->*handler->second.second)(*input, user_info, callback);
+  (this->*handler->second.handler)(*input, user_info, callback);
 }
 
 void PrivetHandler::AddHandler(const std::string& path,
                                ApiHandler handler,
                                AuthScope scope) {
-  CHECK(handlers_.emplace(path, std::make_pair(scope, handler)).second);
+  HandlerParameters params;
+  params.handler = handler;
+  params.scope = scope;
+  params.https_only = false;
+  CHECK(handlers_.emplace(path, params).second);
+}
+
+void PrivetHandler::AddSecureHandler(const std::string& path,
+                                     ApiHandler handler,
+                                     AuthScope scope) {
+  HandlerParameters params;
+  params.handler = handler;
+  params.scope = scope;
+  params.https_only = true;
+  CHECK(handlers_.emplace(path, params).second);
 }
 
 void PrivetHandler::HandleInfo(const base::DictionaryValue&,
@@ -642,7 +686,7 @@
     if (!wifi_ || wifi_->GetTypes().empty()) {
       ErrorPtr error;
       Error::AddTo(&error, FROM_HERE, errors::kDomain,
-                   errors::kSetupUnavailable, "WiFi setup unavailible");
+                   errors::kSetupUnavailable, "WiFi setup unavailable");
       return ReturnError(*error, callback);
     }
     wifi->GetString(kSetupStartSsidKey, &ssid);
@@ -720,7 +764,7 @@
   base::DictionaryValue output;
   base::DictionaryValue* defs = cloud_->GetState().DeepCopy();
   output.Set(kStateKey, defs);
-  output.SetString(kFingerprintKey, base::IntToString(state_fingerprint_));
+  output.SetString(kFingerprintKey, std::to_string(state_fingerprint_));
 
   callback.Run(http::kOk, output);
 }
@@ -731,8 +775,7 @@
   base::DictionaryValue output;
   base::DictionaryValue* defs = cloud_->GetCommandDef().DeepCopy();
   output.Set(kCommandsKey, defs);
-  output.SetString(kFingerprintKey,
-                   base::IntToString(command_defs_fingerprint_));
+  output.SetString(kFingerprintKey, std::to_string(command_defs_fingerprint_));
 
   callback.Run(http::kOk, output);
 }
@@ -781,5 +824,82 @@
                         base::Bind(&OnCommandRequestSucceeded, callback));
 }
 
+void PrivetHandler::HandleCheckForUpdates(const base::DictionaryValue& input,
+                                          const UserInfo& user_info,
+                                          const RequestCallback& callback) {
+  int timeout_seconds = -1;
+  input.GetInteger(kWaitTimeoutKey, &timeout_seconds);
+  base::TimeDelta timeout = device_->GetHttpRequestTimeout();
+  // Allow 10 seconds to cut the timeout short to make sure HTTP server doesn't
+  // kill the connection before we have a chance to respond. 10 seconds chosen
+  // at random here without any scientific basis for the value.
+  const base::TimeDelta safety_gap = base::TimeDelta::FromSeconds(10);
+  if (timeout != base::TimeDelta::Max()) {
+    if (timeout > safety_gap)
+      timeout -= safety_gap;
+    else
+      timeout = base::TimeDelta::FromSeconds(0);
+  }
+  if (timeout_seconds >= 0)
+    timeout = std::min(timeout, base::TimeDelta::FromSeconds(timeout_seconds));
+  if (timeout == base::TimeDelta{})
+    return ReplyToUpdateRequest(callback);
+
+  std::string state_fingerprint;
+  std::string commands_fingerprint;
+  input.GetString(kStateFingerprintKey, &state_fingerprint);
+  input.GetString(kCommandsFingerprintKey, &commands_fingerprint);
+  const bool ignore_state = state_fingerprint.empty();
+  const bool ignore_commands = commands_fingerprint.empty();
+  // If both fingerprints are missing, nothing to wait for, return immediately.
+  if (ignore_state && ignore_commands)
+    return ReplyToUpdateRequest(callback);
+  // If the current state fingerprint is different from the requested one,
+  // return new fingerprints.
+  if (!ignore_state && state_fingerprint != std::to_string(state_fingerprint_))
+    return ReplyToUpdateRequest(callback);
+  // If the current commands fingerprint is different from the requested one,
+  // return new fingerprints.
+  if (!ignore_commands &&
+      commands_fingerprint != std::to_string(command_defs_fingerprint_)) {
+    return ReplyToUpdateRequest(callback);
+  }
+
+  UpdateRequestParameters params;
+  params.request_id = ++last_update_request_id_;
+  params.callback = callback;
+  params.command_defs_fingerprint =
+      ignore_commands ? -1 : command_defs_fingerprint_;
+  params.state_fingerprint = ignore_state ? -1 : state_fingerprint_;
+  update_requests_.push_back(params);
+  if (timeout != base::TimeDelta::Max()) {
+    device_->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&PrivetHandler::OnUpdateRequestTimeout,
+                   weak_ptr_factory_.GetWeakPtr(), last_update_request_id_),
+        timeout);
+  }
+}
+
+void PrivetHandler::ReplyToUpdateRequest(
+    const RequestCallback& callback) const {
+  base::DictionaryValue output;
+  output.SetString(kStateFingerprintKey, std::to_string(state_fingerprint_));
+  output.SetString(kCommandsFingerprintKey,
+                   std::to_string(command_defs_fingerprint_));
+  callback.Run(http::kOk, output);
+}
+
+void PrivetHandler::OnUpdateRequestTimeout(int update_request_id) {
+  auto pred = [update_request_id](const UpdateRequestParameters& params) {
+    return params.request_id != update_request_id;
+  };
+  auto last =
+      std::partition(update_requests_.begin(), update_requests_.end(), pred);
+  for (auto p = last; p != update_requests_.end(); ++p)
+    ReplyToUpdateRequest(p->callback);
+  update_requests_.erase(last, update_requests_.end());
+}
+
 }  // namespace privet
 }  // namespace weave
diff --git a/src/privet/privet_handler.h b/src/privet/privet_handler.h
index 7818d87..fb9cc94 100644
--- a/src/privet/privet_handler.h
+++ b/src/privet/privet_handler.h
@@ -54,7 +54,7 @@
   // Handles HTTP/HTTPS Privet request.
   // |api| is the path from the HTTP request, e.g /privet/info.
   // |auth_header| is the Authentication header from HTTP request.
-  // |input| is the the POST data from HTTP request. If nullptr, data format is
+  // |input| is the POST data from HTTP request. If nullptr, data format is
   // not valid JSON.
   // |callback| will be called exactly once during or after |HandleRequest|
   // call.
@@ -68,8 +68,14 @@
                                              const UserInfo&,
                                              const RequestCallback&);
 
+  // Adds a handler for both HTTP and HTTPS interfaces.
   void AddHandler(const std::string& path, ApiHandler handler, AuthScope scope);
 
+  // Adds a handler for both HTTPS interface only.
+  void AddSecureHandler(const std::string& path,
+                        ApiHandler handler,
+                        AuthScope scope);
+
   void HandleInfo(const base::DictionaryValue&,
                   const UserInfo& user_info,
                   const RequestCallback& callback);
@@ -109,15 +115,34 @@
   void HandleCommandsCancel(const base::DictionaryValue& input,
                             const UserInfo& user_info,
                             const RequestCallback& callback);
+  void HandleCheckForUpdates(const base::DictionaryValue& input,
+                             const UserInfo& user_info,
+                             const RequestCallback& callback);
 
   void ReplyWithSetupStatus(const RequestCallback& callback) const;
+  void ReplyToUpdateRequest(const RequestCallback& callback) const;
+  void OnUpdateRequestTimeout(int update_request_id);
 
   CloudDelegate* cloud_ = nullptr;
   DeviceDelegate* device_ = nullptr;
   SecurityDelegate* security_ = nullptr;
   WifiDelegate* wifi_ = nullptr;
 
-  std::map<std::string, std::pair<AuthScope, ApiHandler>> handlers_;
+  struct HandlerParameters {
+    ApiHandler handler;
+    AuthScope scope;
+    bool https_only = true;
+  };
+  std::map<std::string, HandlerParameters> handlers_;
+
+  struct UpdateRequestParameters {
+    RequestCallback callback;
+    int request_id = 0;
+    int state_fingerprint = -1;
+    int command_defs_fingerprint = -1;
+  };
+  std::vector<UpdateRequestParameters> update_requests_;
+  int last_update_request_id_{0};
 
   uint64_t last_user_id_{0};
   int state_fingerprint_{0};
diff --git a/src/privet/privet_handler_unittest.cc b/src/privet/privet_handler_unittest.cc
index d430935..c212e54 100644
--- a/src/privet/privet_handler_unittest.cc
+++ b/src/privet/privet_handler_unittest.cc
@@ -24,6 +24,7 @@
 using testing::Invoke;
 using testing::Return;
 using testing::SetArgPointee;
+using testing::SaveArg;
 using testing::WithArgs;
 
 namespace weave {
@@ -152,6 +153,9 @@
                             base::Bind(&PrivetHandlerTest::HandlerNoFound));
   }
 
+  const base::DictionaryValue& GetResponse() const { return output_; }
+  int GetResponseCount() const { return response_count_; }
+
   void SetNoWifiAndGcd() {
     handler_.reset(new PrivetHandler(&cloud_, &device_, &security_, nullptr));
     EXPECT_CALL(cloud_, GetCloudId()).WillRepeatedly(Return(""));
@@ -173,6 +177,8 @@
 
  private:
   void HandlerCallback(int status, const base::DictionaryValue& output) {
+    output_.Clear();
+    ++response_count_;
     output_.MergeDictionary(&output);
     if (!output_.HasKey("error")) {
       EXPECT_EQ(200, status);
@@ -188,6 +194,7 @@
 
   std::unique_ptr<PrivetHandler> handler_;
   base::DictionaryValue output_;
+  int response_count_{0};
   ConnectionState gcd_disabled_state_{ConnectionState::kDisabled};
 };
 
@@ -725,5 +732,238 @@
                HandleRequest("/privet/v3/commands/list", "{}"));
 }
 
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_NoInput) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  cloud_.NotifyOnCommandDefsChanged();
+  cloud_.NotifyOnStateChanged();
+  const char kInput[] = "{}";
+  const char kExpected[] = R"({
+   'commandsFingerprint': '1',
+   'stateFingerprint': '1'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(1, GetResponseCount());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_AlreadyChanged) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  cloud_.NotifyOnCommandDefsChanged();
+  cloud_.NotifyOnStateChanged();
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  const char kExpected[] = R"({
+   'commandsFingerprint': '1',
+   'stateFingerprint': '1'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(1, GetResponseCount());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_LongPollCommands) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  cloud_.NotifyOnCommandDefsChanged();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '1',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_LongPollState) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  cloud_.NotifyOnStateChanged();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '1'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_LongPollIgnoreCommands) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  const char kInput[] = R"({
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  cloud_.NotifyOnCommandDefsChanged();
+  EXPECT_EQ(0, GetResponseCount());
+  cloud_.NotifyOnStateChanged();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '1',
+   'stateFingerprint': '1'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_LongPollIgnoreState) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  cloud_.NotifyOnStateChanged();
+  EXPECT_EQ(0, GetResponseCount());
+  cloud_.NotifyOnCommandDefsChanged();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '1',
+   'stateFingerprint': '1'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_InstantTimeout) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0',
+   'waitTimeout': 0
+  })";
+  const char kExpected[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_UserTimeout) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0',
+   'waitTimeout': 3
+  })";
+  base::Closure callback;
+  EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(3)))
+      .WillOnce(SaveArg<1>(&callback));
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  callback.Run();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_ServerTimeout) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::FromMinutes(1)));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  base::Closure callback;
+  EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(50)))
+      .WillOnce(SaveArg<1>(&callback));
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  callback.Run();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_VeryShortServerTimeout) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::FromSeconds(5)));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, kInput,
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(1, GetResponseCount());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_ServerAndUserTimeout) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::FromMinutes(1)));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0',
+   'waitTimeout': 10
+  })";
+  base::Closure callback;
+  EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(10)))
+      .WillOnce(SaveArg<1>(&callback));
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  callback.Run();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+}
+
+TEST_F(PrivetHandlerSetupTest, CheckForUpdates_ChangeBeforeTimeout) {
+  EXPECT_CALL(device_, GetHttpRequestTimeout())
+      .WillOnce(Return(base::TimeDelta::Max()));
+  const char kInput[] = R"({
+   'commandsFingerprint': '0',
+   'stateFingerprint': '0',
+   'waitTimeout': 10
+  })";
+  base::Closure callback;
+  EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(10)))
+      .WillOnce(SaveArg<1>(&callback));
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/checkForUpdates", kInput));
+  EXPECT_EQ(0, GetResponseCount());
+  cloud_.NotifyOnCommandDefsChanged();
+  EXPECT_EQ(1, GetResponseCount());
+  const char kExpected[] = R"({
+   'commandsFingerprint': '1',
+   'stateFingerprint': '0'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, GetResponse());
+  callback.Run();
+  EXPECT_EQ(1, GetResponseCount());
+}
+
+
 }  // namespace privet
 }  // namespace weave
diff --git a/src/privet/privet_manager.cc b/src/privet/privet_manager.cc
index 7a97570..edba589 100644
--- a/src/privet/privet_manager.cc
+++ b/src/privet/privet_manager.cc
@@ -50,8 +50,9 @@
                     StateManager* state_manager) {
   disable_security_ = device->GetSettings().disable_security;
 
-  device_ = DeviceDelegate::CreateDefault(http_server->GetHttpPort(),
-                                          http_server->GetHttpsPort());
+  device_ = DeviceDelegate::CreateDefault(
+      task_runner_, http_server->GetHttpPort(), http_server->GetHttpsPort(),
+      http_server->GetRequestTimeout());
   cloud_ = CloudDelegate::CreateDefault(task_runner_, device, command_manager,
                                         state_manager);
   cloud_observer_.Add(cloud_.get());
diff --git a/src/weave_unittest.cc b/src/weave_unittest.cc
index 63d3348..f009814 100644
--- a/src/weave_unittest.cc
+++ b/src/weave_unittest.cc
@@ -215,6 +215,8 @@
   void InitHttpServer() {
     EXPECT_CALL(http_server_, GetHttpPort()).WillRepeatedly(Return(11));
     EXPECT_CALL(http_server_, GetHttpsPort()).WillRepeatedly(Return(12));
+    EXPECT_CALL(http_server_, GetRequestTimeout())
+        .WillRepeatedly(Return(base::TimeDelta::Max()));
     EXPECT_CALL(http_server_, GetHttpsCertificateFingerprint())
         .WillRepeatedly(Return(std::vector<uint8_t>{1, 2, 3}));
     EXPECT_CALL(http_server_, AddHttpRequestHandler(_, _))
@@ -250,7 +252,8 @@
                   "/privet/v3/pairing/confirm", "/privet/v3/pairing/start"}),
               GetKeys(http_handlers_));
     EXPECT_EQ((std::set<std::string>{
-                  "/privet/info", "/privet/v3/auth", "/privet/v3/commandDefs",
+                  "/privet/info", "/privet/v3/auth",
+                  "/privet/v3/checkForUpdates", "/privet/v3/commandDefs",
                   "/privet/v3/commands/cancel", "/privet/v3/commands/execute",
                   "/privet/v3/commands/list", "/privet/v3/commands/status",
                   "/privet/v3/pairing/cancel", "/privet/v3/pairing/confirm",