Merge remote-tracking branch 'weave/master' into merge BUG: 23908251
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",