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",